Before you start: the OMS API is in early access. Every endpoint, including the ones in this guide, requires an early-access API key. Request access before you begin.
Register your endpoint
Create the subscription withPOST /webhooks. The response returns a signingKey exactly once.
Verify the signature
Every delivery carries aWebhook-Signature header:
v1 is a hex HMAC-SHA256 over the string <t>.<raw request body>, keyed with your signing key. Two details matter:
- Hash the raw body, before any JSON parsing. Re-serializing the parsed object changes the bytes and the signature will not match.
- Compare in constant time, and reject deliveries whose
tis too old to limit replay. OMS regeneratestand the signature on every attempt, so a legitimate retry always carries a fresh timestamp.
Respond before you process
Return a2xx as soon as you have verified and persisted the delivery, then do the real work asynchronously. Each attempt is bounded by the webhook’s timeoutMs (default 5000, hard cap 10000), measured from connect through response. Anything slower is recorded as a failed attempt and retried, even if your handler eventually succeeded, which turns slow processing into duplicate work.
Any non-2xx response, timeout, or network error is retried. Status codes are not special-cased: returning 410 Gone does not stop delivery.
Deduplicate on id
The envelope’s id is the delivery ID (whd_). It is one per event and endpoint, and it stays the same across that delivery’s retries, which makes it your deduplication key.
Because delivery is at-least-once, the same id can arrive more than once: OMS may succeed in sending and fail to record it, then send again. Persist id with a unique constraint and drop a delivery you have already applied.
The five
*.statusChanged events also carry eventId, the producer event ID (evt_). That ID is shared across the fan-out to every endpoint subscribed to the same event, so it is not a per-endpoint dedup key. Deduplicate on id.Order by sequence
Ordering is best effort, not a guarantee. Retries and concurrency mean an older event can arrive after a newer one for the same resource.
sequence is a per-resource monotonic counter. Apply a delivery only when its sequence is newer than the last one you applied for that resource, so a late arrival cannot overwrite newer state. Where a gap matters, refetch the resource from the API instead of inferring the missing step.
Read the event type
Most events name the type inevent. The five *.statusChanged events use eventType. Read whichever is present:
data in both shapes, so branch on data.status rather than parsing the event name for a status suffix.
Test your endpoint
POST /webhooks/{webhookId}/test sends a webhook.test event. It is delivered regardless of your subscriptions, so it exercises signing and reachability before any real event fires.
GET /webhooks/{webhookId}/deliveries, filtering by status, eventId, test, or a createdAfter/createdBefore window. GET /webhooks/{webhookId}/deliveries/{deliveryId} expands a single delivery with its HTTP attempts, including the response status and duration OMS recorded for each one, which is the fastest way to confirm whether a failure was your endpoint or the network.
When deliveries fail
A failed delivery is retried on a fixed schedule:
That is 10 attempts across roughly 3 days, after which the delivery is marked
failed. Requeue failed deliveries yourself with POST /webhooks/{webhookId}/deliveries/retry, passing explicit deliveryIds, or a status plus a createdAfter/createdBefore range. A manual retry resets the attempt count.
If your endpoint stays down, two things happen on separate clocks:
- Notification after the endpoint has been failing longer than
notifyThresholdSecs(10 minutes to 1 day, default 1 hour). Sent once per down episode, not per failed attempt. - Auto-suspension after roughly 3 days of continuous failure.
skipped rather than queued, so nothing is silently dropped while you fix the endpoint. Only OMS sets suspended, and it never clears it on its own: call POST /webhooks/{webhookId}/enable to return the webhook to enabled and reset the down clock, then requeue the skipped deliveries with POST /webhooks/{webhookId}/deliveries/retry.
Delivery history is retained for 30 days by default. DELETE /webhooks/{webhookId} is a soft delete: the webhook disappears from list and get, but its history is kept for the retention window.