Callbacks API overview
The Callbacks API defines the outbound HTTP webhook requests dispatched by Ozeki SMS Gateway to your application's callback endpoint URL. Unlike the Messages API and Webhook API (where your application acts as the HTTP client calling the gateway), for callbacks the direction reverses: the gateway acts as the HTTP client and sends HTTP POST requests carrying batches of event envelopes to the HTTP or HTTPS URL exposed by your application.
Table of contents
Target endpoint & direction
All callback deliveries are sent via HTTP POST to the target URL configured by your application:
POST <client_webhook_url>
- Account-level webhook URL: Registered via
POST /api/v2/webhookorPUT /api/v2/webhook. Receives subscribed"message.received"and/or"message.status.updated"events. - Per-message webhook URL: Specified in the
webhook.urlfield when submitting outbound SMS messages viaPOST /api/v2/messagesorPOST /api/v2/messages:batchSend. Receives"message.status.updated"events for those specific messages.
Headers & authenticity
Every webhook callback HTTP POST request sent by the gateway includes the following headers:
| Header | Presence | Description |
Content-Type |
Always | application/json |
X-Hub-Signature-256 |
When an account secret is configured |
Lowercase hexadecimal HMAC-SHA256 signature of the raw UTF-8 request body, prefixed with sha256= (e.g., sha256=9f83a04b...), computed using the user account's shared webhook secret (whsec_...). Omitted only when dispatching to a per-message webhook.url if no account-level webhook secret is configured. |
Data format conventions
- Always a JSON array: The root request body (
WebhookDeliveryPayload) is always formatted as a JSON array of event envelopes (WebhookEventEnvelope[]), never as a standalone JSON object—even when delivering a single event (an array of length1, up to100events per delivery request). - Heterogeneous event batches: A single webhook delivery array may contain multiple events of different types (mixing
"message.received"and"message.status.updated"in the same HTTPPOSTrequest). Receiving endpoints must deserialize the root body as a list/array and inspect thetypediscriminator on each envelope item. - Property naming: JSON field names are strictly case-sensitive and use
camelCase. - Null omission: Properties with
nullvalues insidepayload(such as an omittedfromsender or unused text/binary fields) are omitted from the serialized JSON output. - Timestamps: Both the envelope
timeand payload timestamps (createdAt,updatedAt) are formatted as ISO 8601 strings with timezone offsets (e.g.,"2026-10-21T10:21:00+02:00"). The envelopetimeindicates when the event actually occurred, not when the HTTP delivery attempt took place.
Callback events summary
Event type (type) |
Payload model | Trigger condition | Documentation |
"message.received" |
MessageReceivedPayload |
Triggered whenever a new inbound SMS message arrives in the user account's inbox folder. |
webhook_message_received.html |
"message.status.updated" |
MessageStatusUpdatedPayload |
Triggered whenever an outbound SMS message transitions delivery status (into the sent or notsent folder, or updating its delivery/view state). |
webhook_message_status_updated.html |
Core concepts
Array payload & event envelope (WebhookEventEnvelope)
Every item in the root JSON array conforms to the WebhookEventEnvelope schema, acting as a polymorphic discriminated union where type determines the structure of payload:
| Field | Type | Description |
eventId |
string (UUID) | Unique identifier for this webhook event. Preserved across retry attempts; use this value for deduplication and idempotency. |
time |
string (ISO 8601) | Timestamp when the underlying event occurred (with timezone offset). |
type |
string | Event type discriminator: "message.received" or "message.status.updated". |
user |
string | Account username associated with the event. |
payload |
object | Event-specific data object: MessageReceivedPayload when type is "message.received", or MessageStatusUpdatedPayload when type is "message.status.updated". |
Delivery, batching & retry policy
- Batching: Up to
100queued events destined for the same target URL are batched into a single HTTPPOSTrequest. - Ordering: Events targeting a given destination URL are delivered sequentially in FIFO order. Global delivery order across different destination URLs is not guaranteed.
- Retries & quadratic backoff: If your callback endpoint does not respond with a
2xxHTTP status code (or if the request times out), the dispatcher retries the delivery up to20times using quadratic backoff:delay = base + (base × retry2) seconds, wherebase = 60 seconds.
Signature verification (X-Hub-Signature-256)
When an account-level webhook signing secret (whsec_...) is configured, your receiving server should verify the X-Hub-Signature-256 header before processing the event batch:
- Extract header: Read the
X-Hub-Signature-256HTTP header and verify that it is present and begins with thesha256=prefix. - Read raw body bytes: Capture the raw UTF-8 request body bytes directly from the HTTP input stream. Do not parse the JSON into an object model and re-serialize it beforehand, as whitespace or key-order changes will invalidate the signature.
- Compute HMAC-SHA256: Calculate the HMAC-SHA256 hash over the raw request body bytes using the UTF-8 bytes of your shared webhook
secret(whsec_...). Format the digest as lowercase hexadecimal prefixed withsha256=. - Constant-time comparison: Compare your computed signature string against the received
X-Hub-Signature-256header value using a constant-time equality function (such asCryptographicOperations.FixedTimeEqualsin .NET) to prevent timing attacks.
Idempotency & client response requirements
- Idempotency / deduplication: Because network failures or timeouts can cause a batch to be retried with the exact same
eventIdvalues, your receiving endpoint should record processedeventIdUUIDs and skip any duplicate events that have already been handled. - Client HTTP response: Your endpoint must return a
2xxHTTP status code rapidly to acknowledge receipt.
| Client HTTP status code | Gateway dispatcher behavior |
2xx (200 OK – 299) |
Delivery is marked as acknowledged and removed from the dispatch queue. No response body is required (any returned payload is ignored). |
3xx, 4xx, 5xx, or Timeout |
Delivery is treated as failed and scheduled for retry according to the quadratic backoff policy (up to 20 retries). |