Event delivery
The platform delivers webhook notifications to your server via HTTP POST.
Each webhook reports an event that already happened. An API operation’s 202 Accepted response only confirms that the request was accepted for processing.
Event catalog
Events are grouped by category. Each owning API overview documents every event name, its payload fields, and when it fires:
Every event has a required top-level aud field containing the intended
app_uuid and an event-time timestamp in RFC 3339 / ISO 8601 UTC format, such
as 2026-06-28T14:30:00.000Z.
Delivery requests use W3C traceparent and tracestate headers for distributed
tracing.
After verifying the signature, take these steps:
- Require
audto equal your configured application UUID. - Process events in the partition order described in Ordering guarantees.
HTTP POST delivery format
Each request has a JSON body and headers for identification, deduplication, and security.
Request headers
Signature verification
Vorbal follows the Standard Webhooks specification and signs every delivery with the cluster’s Ed25519 key. Verification proves that the raw request body and delivery metadata came from Vorbal and were not modified in transit.
Signing is cluster-wide and always active. You fetch public verification keys; you never receive or store a shared signing secret.
For example, a delivery includes these signature headers:
Signing input
For each v1a entry, Vorbal signs this exact byte sequence:
The first two separators are literal period bytes (0x2e). The raw request
body follows the second period without reformatting or a trailing newline.
Use the raw bytes. Verify before JSON parsing. Parsing and re-encoding JSON can change whitespace, key order, or escaping and invalidate the signature.
Webhook-Id is stable for the logical event. Webhook-Timestamp and
Webhook-Signature are regenerated on each delivery attempt.
Signed audience
Because the verification key is shared by the cluster, every webhook body has
a required top-level aud claim containing the intended application UUID:
After signature verification, require aud to be a string equal to your
configured application UUID. Reject a missing or mismatched audience before
performing side effects. This prevents a valid delivery captured for one
application from being replayed to another application that trusts the same
cluster key.
Fetch public keys
The active Ed25519 public keys are published at an unauthenticated endpoint:
The response has this structure:
Refresh the full key set periodically. During rotation, the endpoint publishes the current and previous public keys while the delivery carries one signature from each corresponding private key. Verify the supplied signatures against the trusted active keys and accept when at least one signature/key pair verifies.
Never trust a public key supplied by the webhook request itself.
Verification order
- Enforce a request-body size limit and retain the raw body bytes.
- Require non-empty
Webhook-Id,Webhook-Timestamp, andWebhook-Signature. Reject aWebhook-Idcontaining.. - Parse
Webhook-Timestampas Unix seconds and reject excessive clock skew, such as more than five minutes. - Parse the space-delimited signature entries and strictly decode every
supported
v1asignature from canonical standard base64. Require at least one well-formedv1aentry. - Construct
<webhook-id>.<webhook-timestamp>.<raw-body>and require at least one signature to verify against the trusted active Ed25519 keys. - Parse and schema-validate the JSON body. Require
audto equal your configured application UUID. - Atomically claim
(aud, Webhook-Id)in durable storage before side effects. Retain completed claims for at least 24 hours.
If another handler owns a live processing lease for the same delivery, return
425 Too Early with Retry-After: 30. Mark the claim complete only after side
effects commit; release it and return non-2xx on failure so delivery can be
retried.
Signature validity is necessary but not sufficient. Always validate freshness,
the signed aud claim, the event schema, and deduplication state.
The examples below verify header syntax, timestamp freshness, signatures, and
the aud claim. Apply the request-size, schema-validation, and durable
deduplication steps above around the verifier.
Verification examples
Node.js
Python
Go
Key rotation
During rotation, Vorbal publishes both active public keys and sends two space-delimited signatures:
A consumer with either trusted key can verify the delivery during the overlap. Refresh the complete public-key set atomically so newly introduced keys become trusted and retired keys do not remain cached indefinitely.
Webhook delivery and retries
The same logical event can reach your endpoint more than once. The first two
delivery cycles can retry once after approximately 500ms; the third cycle
makes one final attempt.
Each individual attempt is allowed up to 10 seconds to complete before it is treated as a timeout.
Delivery window
An event remains eligible for five minutes from its body timestamp. After
that, it moves to the dead-letter queue without an HTTP request. The window can
end before all three cycles or five attempts are used.
After a webhook endpoint outage or processing backlog lasting more than five minutes, do not assume that every event will arrive when service recovers. Expired events create permanent delivery gaps; there is no public replay mechanism.
Response handling
Ordering guarantees
Events in the same resource scope are processed sequentially. A retryable
failure or 425 Too Early holds later events in that scope until the event
succeeds, is dead-lettered, or expires.
The platform selects the ordering scope as follows:
- All
room.*events with the sameroom_id, including member and playback events, share one Room scope. - For other events, the first non-empty identifier in this order defines the
scope:
message_uuid,session_uuid,room_id,recording_uuid, thenoperation_uuid.
For example, message.* events are ordered by message_uuid. Session events
are ordered by session_uuid, so when both arrive,
session.answered arrives before
session.ended.
Different resource scopes can be delivered concurrently and interleaved. Do not infer an order between a Room scope and a Session scope, even when the Session is a member of that Room. The endpoint circuit breaker can defer every scope that uses the same webhook URL.