Appearance
Webhooks for apps beta
The Console tells an app acting for its users what happens in its teams - an order went through, a product is ready - so it does not have to poll.
- Webhook endpoints are set up by Anikeen on request: your app's client id, a display name, the URL and the event types. You get the endpoint's signing secret
whsec_…once (it can be rolled). - Only teams managed by an app produce events, and only that app's active endpoints that listen to the type receive them.
Request format
The same format as Anikeen ID's webhooks, so you verify both with the same code:
http
POST <endpoint url>
Content-Type: application/json
User-Agent: Anikeen-Console-Webhooks/1.0
Anikeen-Event-Id: 01JF8Z…
Anikeen-Event-Type: product.ready
Anikeen-Signature: t=1731150000,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with the secret>
{"id": "01JF8Z…", "type": "product.ready", "created": 1731150000,
"data": {"object": {"id": "01JF…", "family": "container", "team_id": "01JE…", "status": "running", …}}}| Part | |
|---|---|
id | the event id (also in Anikeen-Event-Id) |
type | the event type (also in Anikeen-Event-Type) |
created | Unix timestamp of the event |
data.object | the object - see Events |
data.reason | for product.updated and product.deleted |
The envelope is frozen when the event happens; every attempt sends the same body, signed anew (the signature's timestamp is the attempt's).
Verifying the signature
Anikeen-Signature is t=<unix time>,v1=<signature>, where the signature is the hex HMAC-SHA256 of "<t>.<raw body>" with your endpoint's secret. Always verify against the raw request body, and reject old timestamps (anikeen/id allows 5 minutes by default).
php
use Anikeen\Id\Exceptions\SignatureVerificationException;
use Anikeen\Id\Webhooks\Webhook;
try {
$event = Webhook::constructEvent(
$request->getContent(),
$request->header('Anikeen-Signature'),
$secret,
);
} catch (SignatureVerificationException) {
abort(400);
}
// ['id' => …, 'type' => 'product.ready', 'created' => …, 'data' => ['object' => […]]]php
// routes/web.php - verifies, de-duplicates and dispatches Laravel events
Route::anikeenConsoleWebhooks('webhooks/anikeen-console');js
import crypto from 'node:crypto'
function verify(rawBody, header, secret, tolerance = 300) {
let t = null
const signatures = []
for (const part of (header ?? '').split(',')) {
const [key, value] = part.trim().split('=', 2)
if (key === 't' && /^\d+$/.test(value ?? '')) t = value
else if (key === 'v1' && value) signatures.push(value)
}
if (t === null || signatures.length === 0) return false
const expected = Buffer.from(
crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex'),
)
const matches = signatures.some((s) => {
const given = Buffer.from(s)
return given.length === expected.length && crypto.timingSafeEqual(expected, given)
})
return matches && Math.abs(Date.now() / 1000 - Number(t)) <= tolerance
}See SDK webhooks for the Laravel route and events.
Events
An endpoint lists the types it wants, or * for all; webhook.test is always sent.
| Type | When | data.object |
|---|---|---|
order.completed | the order was confirmed in Anikeen ID, provisioning started | the order as GET /teams/{team}/orders/{order} (checkout_url always null) |
order.failed | the order was cancelled or expired (or its checkout could not be opened) | the order (status cancelled, expired or failed) |
product.ready | a product was built (a container: created and started) | the product |
product.failed | building an item failed (revoked and refunded, as in the Console) | {id: null, family, team_id, status: "failed", order_id, order_item_id, product_id, error} |
product.updated | suspended / resumed for billing, rescaled, reinstalled | the product; data.reason: billing_suspended, billing_resumed, rescaled, reinstalled |
product.deleted | deleted (by the app, the contract ended in Anikeen ID, …) | the product (status: "deleted"); data.reason: deleted or contract_ended |
team.released | the app lost access to the team (see Ending it); the last event for the team | {id, name, app: {id, name}, released_at} |
webhook.test | a test event sent by Anikeen | {message} |
The product object
{id, family, team_id, status, …} plus everything the family's GET /<family>/{id} returns (e.g. a container's location, template, gpu, addresses).
status is the family's live status where it has one and it was read (a container's or server's running, stopped, … on product.ready, rescale and reinstall), otherwise active, provisioning (still being set up beta), suspended (billing) or deleted. On product.ready the product is set up: its provisioning_status is null (see Products being set up).
Read the product from the API when you need its current state: the API is the source of truth.
Delivery and retries
- A
2xxanswer within 10 seconds counts as delivered. Redirects are not followed. - Otherwise the attempt is retried after 1 min, 5 min, 30 min, 2 h, 5 h, 10 h and 3 × 12 h (10 attempts over about two days), then the delivery has failed.
- Events are delivered at least once and not necessarily in order: de-duplicate on the event
id(Anikeen-Event-Id) and answer2xxfor an id you have already handled. - Endpoint URLs must use
httpsand resolve to public addresses only (checked when saved and before every attempt). - Events and deliveries are kept for 30 days; Anikeen can see each delivery's attempts and your endpoint's answers, and resend it.