Skip to main content
When a lead replies from either a phone number or Apple ID email handle, Chert creates or matches the tenant-scoped chat and delivers a real-time message.received event to your webhook subscriptions. Events are durable, signed, and replayable. Subscribe by passing events: ["message.received"] on POST /api/v1/webhook-subscriptions. Event lifecycle

Configuring a webhook

Two options:
  1. Console — your Chert administrator configures a webhook endpoint for your tenant.
  2. API — create a subscription programmatically with POST /api/v1/webhook-subscriptions. See Webhook subscriptions.
The webhook subscription secret is independent from your API signing_secret — rotate either without touching the other. If you cannot expose a public URL during development, use the SSE stream or dev inbox instead.

message.received envelope

Real-time, fires immediately when an inbound iMessage lands.
message.received

Field map

Compat note. Some legacy deliveries also include top-level from, to, text, message_guid, and line_id aliases. New integrations should prefer the data.* paths because they are richer and consistent across Chert-managed lines.

Receiving images and attachments

Inbound media is handled by the Chert API layer automatically. When a lead sends an image or file, the message.received webhook carries a media part in data.message.parts[] with an attachment_id, filename, MIME type, and byte size. Fetch the bytes with the same tenant auth you use for other API calls:
The attachment id is tenant-scoped. If an inbound contains both text and media, keep the array order from data.message.parts[] when reconstructing the message.

Replying

Two working paths. Pick by what you need:
$CHAT_ID is data.chat.id from the webhook. $MESSAGE_GUID is message_guid (or data.message.id) and can be used for tapbacks with /messages/{id}/react. Use a normal chat follow-up for replies.

Headers

These headers are sent by Chert to your webhook endpoint during event delivery. Do not send x-chert-tenant in your webhook receiver — that header is only for requests your application makes to Chert APIs.

Legacy compatibility headers

Chert also emits these older lowercase headers for existing integrations. New integrations should read the recommended X-Webhook-* headers above. Both signature formats compute the same HMAC: HMAC-SHA256(subscription_secret, "<ts>.<raw_body>").

Verifying the signature

Compute the HMAC over the raw request body (exact bytes as received) and compare with constant-time equality. Do not parse and re-serialize the JSON before verifying.
Reject any request that fails verification with 401.

Retry behavior

Failed events may be retried later by Chert. Pull events through GET /api/v1/events and replay manually after fixing your endpoint.

At-least-once semantics

You may receive the same event more than once during retries or replays. Always dedup on event_id (also exposed in the x-chert-event-id header).

Replaying missed events

If your endpoint was down, list events with delivery.status = failed and replay them:
To replay to one subscription instead of every matching active subscription, add ?subscription_id=<subscription_id>. See Events for the full debugging surface.

See also