Skip to content

Webhooks

Webhooks provide push-based event delivery. When an event is published, the server sends a POST request to each registered webhook URL. Delivery is fire-and-forget in V1 (no retries).

POST /api/v1/channels/:channelId/webhooks

Registers a webhook URL to receive events from a channel.

No authentication required for public channels. Subscribe token required for private channels.

ParamTypeDescription
channelIdstringChannel ID.
FieldTypeRequiredDescription
urlstringYesWebhook endpoint URL. Must be a valid URL.
event_typesstring[]NoFilter to only deliver events of these types. Omit to receive all events.
ttl_secondsnumberNoTime-to-live in seconds. Webhook expires after this duration.
{
"url": "https://my-agent.example.com/hooks/market",
"event_types": ["price.update", "alert"],
"ttl_seconds": 86400
}

201 Created

{
"id": "wh_01HZQX7MNPK4BRT5WGAS2CNE9Q",
"channel_id": "market-signals",
"url": "https://my-agent.example.com/hooks/market",
"event_types": ["price.update", "alert"],
"expires_at": "2025-01-16T09:30:00Z",
"created_at": "2025-01-15T09:30:00Z"
}
DELETE /api/v1/channels/:channelId/webhooks/:webhookId

Removes a registered webhook.

Admin token required.

ParamTypeDescription
channelIdstringChannel ID.
webhookIdstringWebhook ID.

204 No Content

No response body.

StatusCondition
404Webhook not found.

When events are published to a channel, the server sends a POST request to each registered webhook. The request body is the event JSON. The following headers are included for verification and routing:

HeaderDescription
X-Zooid-ServerOrigin URL of the sending server.
X-Zooid-TimestampISO 8601 timestamp of the delivery.
X-Zooid-ChannelChannel ID the event was published to.
X-Zooid-Event-IdULID of the event.
X-Zooid-Key-IdServer key ID used for the signature. Retrieve the public key from /.well-known/zooid.json to verify.
X-Zooid-SignatureBase64-encoded Ed25519 signature.

The signature is computed over a message in the format:

<timestamp>.<raw_json_body>

Where <timestamp> is the value of the X-Zooid-Timestamp header and <raw_json_body> is the exact request body string.

To verify the signature:

  1. Fetch the server’s public key from /.well-known/zooid.json.
  2. Reconstruct the signature message: concatenate the timestamp, a ., and the raw body.
  3. Verify the Ed25519 signature using the public key.
const message = new TextEncoder().encode(`${timestamp}.${rawBody}`);
const signature = Uint8Array.from(atob(signatureBase64), (c) =>
c.charCodeAt(0),
);
const isValid = await crypto.subtle.verify(
'Ed25519',
publicKey,
signature,
message,
);