Webhook event catalog

Reference every webhook event Samva delivers, its payload fields, and the headers, retries, and limits of each delivery.

This page is the reference for what Samva sends to your webhook endpoints: the request, the delivery rules, and every event type with its payload. To create an endpoint and handle deliveries, see Receive webhooks.

Envelope

Every delivery is a POST with a JSON body. type is the event type, timestamp is when Samva publishes the event as an ISO 8601 string, and data holds the event's payload, described per event below. For bounces, complaints, tracked opens, and clicks, data.occurredAt records the occurrence time, even when the event is published later.

{
  "type": "message.delivered",
  "timestamp": "2026-01-15T09:42:11.204Z",
  "data": {
    "messageId": "msg_01j8k3m5n7p9q2r4s6tv",
    "organizationId": "org_01j8k0a2b4c6d8e0f2gh",
    "channel": "email",
    "status": "delivered",
    "subject": "Welcome to Samva",
    "toEmails": ["ada@example.com"]
  }
}

A field marked optional is left out when it has no value. Parse data by type, and ignore fields you do not recognize: Samva can add fields to a payload.

Request headers

Each request carries the Standard Webhooks headers, plus any custom headers you configured on the endpoint.

HeaderValue
webhook-idThe event ID, for example whevt_01j8k3m5n7p9q2r4s6tw. Every attempt of the same event carries it.
webhook-timestampWhen this attempt was signed, in Unix seconds. Each attempt gets a new one.
webhook-signatureSpace-separated v1,<base64> signatures, one per active signing secret. Each attempt is re-signed.
content-typeapplication/json.

Signing secrets start with whsec_, followed by a Base64-encoded signing key. Samva shows a secret once, when you create the endpoint or rotate its secret.

Delivery

Samva delivers each event to every active endpoint subscribed to its type. Delivery is at least once and unordered: the same event can arrive more than once, and a later event can arrive before an earlier one. Use webhook-id to process each event once.

An attempt succeeds when your endpoint answers with any 2xx status. Any other status, a redirect (Samva does not follow redirects), a connection error, or a timeout fails the attempt and schedules the next one.

LimitValue
Response timeout per attempt30 seconds
Automatic attempts per event8
Longest Retry-After delay2x the scheduled delay
Endpoint disabled after5 days without a successful delivery
Delivery log retention90 days
Previous secret valid after rotation24 hours
Signature timestamp tolerance5 minutes

Retry schedule

Each delay is measured from the previous failed attempt and varies randomly by a small amount so that retries from many events do not arrive together.

AttemptDelay after the previous attempt
1Immediately
25 seconds
35 minutes
430 minutes
52 hours
65 hours
710 hours
810 hours

A failed response with a Retry-After header, in seconds or as an HTTP date, can push the next attempt later, up to the longest delay in the limits table. It never makes an attempt sooner. After the last attempt fails, the event is not retried again.

When an event runs out of attempts and the endpoint has gone the full disable period without a successful delivery, Samva disables the endpoint. A disabled endpoint receives nothing until you set its status back to active with the update operation.

Tests, redelivery, and logs

  • A test delivery (webhook.test) goes through the same attempts and retries as any other event.
  • A manual redelivery makes one immediate attempt and does not start a new retry series.
  • Delivery logs record each attempt's number, status code, duration, error code, and a fixed set of response headers (cache-control, cf-mitigated, content-length, content-type, date, retry-after, server, x-request-id). Response bodies are never stored.

Signatures

The signature covers the webhook-id, the webhook-timestamp, and the exact request body. A verifier rejects a request whose timestamp is further from its clock than the tolerance in the limits table, so a captured request cannot be replayed later. After you rotate a secret, requests carry a signature for both the new and the previous secret until the previous one expires. Verify webhooks documents the verifier.

Event types

Mailbox events, which report mail received, sent, held, and waiting for approval, are listed in Mailbox events. They reach an endpoint that subscribes to them.

Message events, domain.warming.updated, and contact.consent_changed belong to the channel they report on: an endpoint whose channels list omits email does not receive them for email. Every other event reaches each endpoint subscribed to its type, whatever its channels.

Message events

Message events follow one email from send to delivery, and report inbound email. Every message event carries these fields in data:

FieldTypeNotes
messageIdstringThe message the event is about.
organizationIdstringYour organization.
channelstringemail.
metadataobjectOptional. The metadata you set on the send.

message.sent

Fires when Samva sends the email toward the recipient's mail server. It does not mean the email arrived; message.delivered reports that.

Samva sends each recipient its own copy, so a message with several recipients fires one message.sent per recipient, each with the same messageId. toEmails names that recipient whether the send listed it in to, cc, or bcc.

FieldTypeNotes
statusstringsent.
subjectstringOptional. The subject of the email.
toEmailsstring[]The address this copy was sent to.
providerMessageIdstringThe email service's id for this copy.
{
  "type": "message.sent",
  "timestamp": "2026-01-15T09:42:10.031Z",
  "data": {
    "messageId": "msg_01j8k3m5n7p9q2r4s6tv",
    "organizationId": "org_01j8k0a2b4c6d8e0f2gh",
    "channel": "email",
    "status": "sent",
    "subject": "Welcome to Samva",
    "toEmails": ["ada@example.com"],
    "providerMessageId": "010001923f5c9a1b-6c0a2f4e-8d1b-4a7e-9f3c-2b5d8e1a4c7f-000000",
    "metadata": { "orderId": "ord_1042" }
  }
}

message.delivered

Fires when the recipient's mail server accepts the email.

FieldTypeNotes
statusstringdelivered.
subjectstringOptional. The subject of the email.
toEmailsstring[]Optional. The addresses the email was sent to.

message.bounced

Fires when the recipient's mail server permanently rejects the email, for example because the address does not exist. Samva emits one event per affected recipient, each with its own webhook-id. Use that ID for deduplication; multiple recipients can share the same messageId. recipient names the address that bounced and occurredAt records when the bounce occurred. Samva stops sending to a permanently bounced address; see suppression.

FieldTypeNotes
recipientstringThe address that bounced.
occurredAtstringWhen the bounce occurred, ISO 8601.
statusstringbounced.
failureReasonstringPermanent.
errorCodestringOptional. The bounce kind, such as General or NoEmail.
subjectstringOptional. The subject of the email.
toEmailsstring[]Optional. The addresses the email was sent to.
{
  "type": "message.bounced",
  "timestamp": "2026-01-15T09:43:01.117Z",
  "data": {
    "messageId": "msg_01j8k3m5n7p9q2r4s6tv",
    "organizationId": "org_01j8k0a2b4c6d8e0f2gh",
    "channel": "email",
    "status": "bounced",
    "recipient": "ada@example.com",
    "occurredAt": "2026-01-15T09:43:00.000Z",
    "failureReason": "Permanent",
    "errorCode": "NoEmail",
    "subject": "Welcome to Samva",
    "toEmails": ["ada@example.com"]
  }
}

message.complained

Fires when a recipient reports the email as spam. Samva emits one event per affected recipient, each with its own webhook-id. Use that ID for deduplication; multiple recipients can share the same messageId. recipient names the address that complained and occurredAt records when the complaint occurred. Samva stops sending to an address that complained; see suppression.

FieldTypeNotes
recipientstringThe address that complained.
occurredAtstringWhen the complaint occurred, ISO 8601.
statusstringcomplained.
failureReasonstringOptional. The kind of complaint the mailbox reported, such as abuse.
subjectstringOptional. The subject of the email.
toEmailsstring[]Optional. The addresses the email was sent to.

message.failed

Fires when an email will not be delivered and it did not permanently bounce: Samva did not send it, or the recipient's mail server kept rejecting it temporarily until delivery was abandoned.

FieldTypeNotes
statusstringfailed.
failureReasonstringA readable reason. Transient when the recipient's server gave up.
errorCodestringOptional. A machine-readable code; see the list below.
subjectstringOptional. Present when the recipient's server gave up.
toEmailsstring[]Optional. Present when the recipient's server gave up.

Codes you can act on:

  • SUPPRESSED: a recipient is on your organization's suppression list, so Samva did not send.
  • EMAIL_SEND_EXPIRED: the email did not start sending within its send window.

When the recipient's server gave up, errorCode is the bounce kind, such as MailboxFull. Log any other code with its failureReason.

{
  "type": "message.failed",
  "timestamp": "2026-01-15T09:42:12.507Z",
  "data": {
    "messageId": "msg_01j8k3m5n7p9q2r4s6tv",
    "organizationId": "org_01j8k0a2b4c6d8e0f2gh",
    "channel": "email",
    "status": "failed",
    "failureReason": "Recipient suppressed for organization: ada@example.com",
    "errorCode": "SUPPRESSED"
  }
}

message.opened

Fires when Samva observes an open on a tracked email send. Email tracking follows the effective send settings and recipient preference. Apple Mail Privacy Protection and security scanners can open messages automatically, often producing the first observation, so an open does not necessarily mean a person read the email. Samva emits an event for every observed open. firstOpen is true for the first processed open observation for that delivery that is not classified as machine activity.

FieldTypeNotes
deliveryIdstringThe recipient's delivery.
recipientstringThe recipient's email address.
occurredAtstringISO 8601 observation time of the open.
firstOpenbooleantrue for the first processed non-machine open for this delivery.
userAgentstringOptional. Included by default when present.
ipAddressstringOptional. Full observed IP address when present; may belong to a proxy.
machinebooleanWhether heuristics identify likely automatic activity.
machineReasonstring or nullReason for a likely automatic observation; null otherwise.
countrystringOptional. Country associated with the observed IP address.
citystringOptional. City associated with the observed IP address.
asnnumberOptional. Autonomous system number associated with the observed IP address.
codestringOptional. Tracking code for the observation.
linkIndexnumberOptional. Index of the tracked link in the delivery.
destinationUrlstringOptional. Destination of the tracked link.
campaignIdstringOptional. Present for campaign sends; omitted otherwise.
campaignRunIdstringOptional. Present for campaign sends; omitted otherwise.

The event also carries the common message fields above. metadata is included when the send has customer metadata.

{
  "type": "message.opened",
  "timestamp": "2026-01-15T09:43:10.000Z",
  "data": {
    "messageId": "msg_01j8k3m5n7p9q2r4s6tv",
    "organizationId": "org_01j8k0a2b4c6d8e0f2gh",
    "channel": "email",
    "deliveryId": "mdel_01j8k3m5n7p9q2r4s6tw",
    "recipient": "ada@example.com",
    "occurredAt": "2026-01-15T09:43:08.412Z",
    "firstOpen": true,
    "machine": false,
    "machineReason": null,
    "userAgent": "Mozilla/5.0",
    "ipAddress": "203.0.113.8",
    "metadata": { "orderId": "ord_1042" }
  }
}

message.clicked

Fires when Samva observes a click on a tracked link in a tracked email send. Email tracking follows the effective send settings and recipient preference. Security scanners can follow links automatically, often producing the first observation, so a click does not necessarily mean a person clicked the link. Apple Mail Privacy Protection can also produce automatic opens before any human interaction. firstClick is true for the first processed click observation for that delivery that is not classified as machine activity.

FieldTypeNotes
deliveryIdstringThe recipient's delivery.
recipientstringThe recipient's email address.
occurredAtstringISO 8601 observation time of the click.
firstClickbooleantrue for the first processed non-machine click for this delivery.
urlstringThe destination URL.
linkTagsRecord<string, string[]>Link tags, or {} when none are set.
userAgentstringOptional. Included by default when present.
ipAddressstringOptional. Full observed IP address when present; may belong to a proxy.
machinebooleanWhether heuristics identify likely automatic activity.
machineReasonstring or nullReason for a likely automatic observation; null otherwise.
countrystringOptional. Country associated with the observed IP address.
citystringOptional. City associated with the observed IP address.
asnnumberOptional. Autonomous system number associated with the observed IP address.
codestringOptional. Tracking code for the observation.
linkIndexnumberOptional. Index of the tracked link in the delivery.
destinationUrlstringOptional. Destination of the tracked link.
campaignIdstringOptional. Present for campaign sends; omitted otherwise.
campaignRunIdstringOptional. Present for campaign sends; omitted otherwise.

The event also carries the common message fields above. metadata is included when the send has customer metadata. Every observed click on a tracked send produces an event; firstClick only marks the first non-machine observation processed for that delivery.

{
  "type": "message.clicked",
  "timestamp": "2026-01-15T09:44:10.000Z",
  "data": {
    "messageId": "msg_01j8k3m5n7p9q2r4s6tv",
    "organizationId": "org_01j8k0a2b4c6d8e0f2gh",
    "channel": "email",
    "deliveryId": "mdel_01j8k3m5n7p9q2r4s6tw",
    "recipient": "ada@example.com",
    "occurredAt": "2026-01-15T09:44:08.412Z",
    "firstClick": true,
    "machine": false,
    "machineReason": null,
    "url": "https://shop.example.com/orders/1042",
    "linkTags": { "order": ["confirmation"] },
    "userAgent": "Mozilla/5.0",
    "ipAddress": "203.0.113.8",
    "metadata": { "orderId": "ord_1042" }
  }
}

Likely automatic observations still produce events, with firstOpen or firstClick set to false. Their machine classification is a heuristic, not proof of who acted; unique open and click analytics exclude observations marked as machine activity. Full IP addresses follow the same retention policy as other message events. See Control engagement tracking for tracking controls and data collection.

message.received

Fires when Samva receives an inbound email at an address on a domain with receiving enabled. It goes to the endpoint the domain's receiving route names; that endpoint must subscribe to message.received.

FieldTypeNotes
directionstringinbound.
fromstringThe sender's address.
tostringThe first of your addresses the email was routed through.
toEmailsstring[]Every one of your addresses the email was routed through for this endpoint.
conversationIdstringThe conversation the email was threaded into.
subjectstringOptional. The subject of the email.
hasAttachmentsbooleanWhether the email had attachments.
isAutoReplybooleanWhether the email looks like an automatic reply.
bodystringOptional. The HTML body when there is one, otherwise the text body.
{
  "type": "message.received",
  "timestamp": "2026-01-15T10:05:33.900Z",
  "data": {
    "messageId": "msg_01j8k4n6p8q2r4s6t9vw",
    "organizationId": "org_01j8k0a2b4c6d8e0f2gh",
    "channel": "email",
    "direction": "inbound",
    "from": "ada@example.com",
    "to": "support@your-domain.com",
    "toEmails": ["support@your-domain.com"],
    "subject": "Re: Welcome to Samva",
    "conversationId": "conv_01j8k5p7q9r2s4t6v8wx",
    "hasAttachments": false,
    "isAutoReply": false,
    "body": "Thanks for the welcome. How do I invite a teammate?"
  }
}

Contact events

contact.created, contact.updated, and contact.deleted carry the whole contact as it stands after the change, so you can sync it without having seen earlier events. Custom fields and the human profile are not included; read the contact through the API when you need them.

FieldTypeNotes
contactIdstringThe contact.
statusstringactive, paused, blocked, or archived.
namestringThe contact's name.
displayNamestringOptional, may be null.
identitiesobject[]Each with type (email or phone), identifier (the normalized address), and verified.
tagsstring[]The contact's tags.
externalIdstringOptional, may be null. Your own ID for the contact.
createdAtstringISO 8601.
updatedAtstringISO 8601.

contact.created

Fires when a contact is created, one at a time or by import.

contact.updated

Fires when a contact changes, including bulk tag and status changes.

contact.deleted

Fires when a contact is deleted, one at a time or in bulk. The payload is the contact as it was just before deletion.

contact.subscribed

Fires after the recipient explicitly confirms a double opt-in request. It reports the confirmed topic subscription, rather than the contact snapshot above. Each successfully consumed fresh link emits an event, even when the contact is already subscribed. Reusing the same link fails without another event.

FieldTypeNotes
organizationIdstringYour organization.
contactIdstringThe confirmed contact.
emailstringOptional. The confirmed email address.
topicIdstringThe topic subscribed.
topicNamestringThe topic's name.
channelstringemail.
sourcestringdouble_opt_in.
consentIpstringOptional. Signup IP supplied by your server.
consentSourcestringOptional. Signup source supplied by your server.
occurredAtstringConfirmation time, ISO 8601.

A confirmed public form signup has source form and can carry these additional fields:

FieldTypeNotes
formKeystringOptional. The public key of the signup form.
pagestringOptional. The signup page supplied by the form.
referrerstringOptional. The referrer supplied by the form.

Public form signups record the client IP observed by Samva. The page and referrer remain caller-supplied.

A confirmed subscribe wall signup has source wall and consentSource: "wall". It has no formKey, and page is the issue URL, https://<host>/p/<issue-slug>, set by Samva.

A confirmed signup from a publication's home page form or the form after an open issue has source publication and consentSource: "publication". It has no formKey, and page is the home page URL, https://<host>/, or the issue URL, set by Samva.

The signup context on an authenticated confirmation request is caller-supplied. It does not assert that Samva independently verified the IP address or signup source. The event never includes the confirmation token or URL.

contact.unsubscribed

Fires once for each topic whose subscription changes to unsubscribed. This includes recipient links and preference pages (source: "link") and API changes (source: "api"). The optional messageId is present only when the link names a message sent to the same recipient in the same organization, and the changed topic is the topic in that message's original unsubscribe link. Other changed topics have no message attribution. Campaign fields are included only for an attributed campaign delivery. contactId is always present: an API unsubscribe that names an address no contact holds creates the contact. email is present when the contact was addressed by email. channel names the channel the subscription belongs to.

{
  "type": "contact.unsubscribed",
  "timestamp": "2026-01-15T09:43:10.000Z",
  "data": {
    "organizationId": "org_01j8k0a2b4c6d8e0f2gh",
    "contactId": "cont_01j8k3m5n7p9q2r4s6tv",
    "email": "ada@example.com",
    "topicId": "unsub_01j8k3m5n7p9q2r4s6tw",
    "topicName": "Product updates",
    "channel": "email",
    "source": "link",
    "messageId": "msg_01j8k3m5n7p9q2r4s6tv",
    "occurredAt": "2026-01-15T09:43:08.412Z"
  }
}

contact.resubscribed

Fires once for each topic whose subscription changes back to subscribed. Sources and optional message and campaign fields follow contact.unsubscribed; API changes use source: "api". contactId is always present, and email is present when the contact was addressed by email.

If webhook publication fails, recipient links and preference pages keep opt-outs. A preference-page resubscribe is not applied, and the request fails so the recipient can retry, even when opt-outs in the same request are kept. One-click unsubscribe still succeeds. API subscription changes fail without applying any changes.

{
  "type": "contact.resubscribed",
  "timestamp": "2026-01-15T09:43:10.000Z",
  "data": {
    "organizationId": "org_01j8k0a2b4c6d8e0f2gh",
    "contactId": "cont_01j8k3m5n7p9q2r4s6tv",
    "email": "ada@example.com",
    "topicId": "unsub_01j8k3m5n7p9q2r4s6tw",
    "topicName": "Product updates",
    "channel": "email",
    "source": "api",
    "occurredAt": "2026-01-15T09:43:08.412Z"
  }
}

contact.consent_changed

Fires when a contact's consent on a channel at one of its addresses changes: when you record it through the API, or when Samva records it from a recipient's confirmation. Recording the status the address already has publishes nothing.

FieldTypeNotes
organizationIdstringYour organization.
contactIdstringThe contact.
identityobjectThe address, with id, type (email or phone), and identifier.
channelstringThe channel the consent covers.
statusstringopted_in or opted_out.
previousStatusstringopted_in, opted_out, or null when none was recorded.
sourcestringWhere the decision was captured.
capturedAtstringWhen the contact decided, ISO 8601.
{
  "type": "contact.consent_changed",
  "timestamp": "2026-01-15T09:43:10.000Z",
  "data": {
    "organizationId": "org_01j8k0a2b4c6d8e0f2gh",
    "contactId": "cont_01j8k3m5n7p9q2r4s6tv",
    "identity": {
      "id": "cident_01j8k3m5n7p9q2r4s6tx",
      "type": "email",
      "identifier": "ada@example.com"
    },
    "channel": "email",
    "status": "opted_out",
    "previousStatus": null,
    "source": "Support request",
    "capturedAt": "2026-01-15T09:43:08.412Z"
  }
}

Conversation events

Conversation events carry the conversation as it stands after the change. Read its counters, contacts, tags, and metadata with GET /v1/conversations/{id}.

FieldTypeNotes
conversationIdstringThe conversation.
organizationIdstringYour organization.
titlestringOptional, may be null.
channelsstring[]["email"].
primaryChannelstringemail.
statusstringactive, waiting, resolved, escalated, or archived.
primaryContactIdstringOptional, may be null.
createdAtstringISO 8601.
updatedAtstringISO 8601.

conversation.created

Fires when a conversation starts, including when an inbound email starts a new thread.

conversation.updated

Fires when a conversation's details or participants change.

conversation.archived

Fires when a conversation's status changes to archived. That change fires only this event, not conversation.updated.

Domain events

domain.updated

Fires when a sending domain moves between verification phases, including when a verified domain degrades and when it recovers. Verify your domain explains each state.

FieldTypeNotes
domainIdstringThe domain.
organizationIdstringYour organization.
domainstringThe domain name.
previousPhasestringThe phase before this change.
phasestringdns-setup, provisioning, verifying, ready, degraded, or failed.
dnsobjectRecord counts: observed (whether Samva has read the records yet), total, satisfied, missing, mismatch, and unknown.
providerobjectMay be null. Sending-side checks: identity, and optional dkim and mailFrom, each pending, verified, failed, or unknown.
operationIdstringMay be null. The verification run that made this change.
checkedAtstringMay be null. When Samva last checked the domain, ISO 8601.
nextCheckAtstringMay be null. When Samva checks the domain next, ISO 8601.
failureobjectMay be null. code, message, and failedAt for the latest failure.
updatedAtstringISO 8601.

domain.warming.updated

Fires for every decision on a domain's warming plan, whether you made it or Samva's policy did.

FieldTypeNotes
domainIdstringThe domain.
organizationIdstringYour organization.
domainstringThe domain name.
planIdstringThe warming plan.
reasonstringplan-created, policy-updated, window-opened, window-cancelled, paused, resumed, held, or completed.
statusstringThe plan's status: active, paused, or completed.
holdReasonstringMay be null. Why the plan is held.
batchobjectMay be null. The window the decision is about: id, sequence, startsAt, endsAt, allowanceRecipients, dailyGrowthPercent, and targetDailyRecipients.
updatedAtstringISO 8601.

Webhook endpoint events

Endpoint events describe your webhook endpoints. They never carry a signing secret or custom header values.

FieldTypeNotes
webhookIdstringThe endpoint the event describes.
organizationIdstringYour organization.
namestringThe endpoint's name.
urlstringThe endpoint's URL.
eventTypesstring[]The event types it subscribes to.
channelsstring[]May be null, meaning every channel.
statusstringactive or disabled.
createdAtstringISO 8601.
updatedAtstringISO 8601.

webhook.created

Fires when an endpoint is created. A new endpoint receives it only if it subscribes to webhook.created itself.

webhook.updated

Fires when an endpoint changes.

webhook.deleted

Fires when an endpoint is deleted. The payload is the endpoint as it was just before deletion. The deleted endpoint does not receive it.

webhook.test

Fires when you send a test delivery to an endpoint. It goes only to that endpoint, whether or not the endpoint subscribes to webhook.test. data is the object you pass to the test operation, or {}.

Subscribing

Set eventTypes when you create or update an endpoint, using the type strings above. Register more than one endpoint to route different events to different services.

Next steps

Related documentation

On this page