Skip to main content
Sequenzy webhooks dashboard with callouts showing how to add a webhook or use the API
You can send email lifecycle events to your own HTTPS endpoint when messages are sent, delivered, delayed, bounced, complained, opened, clicked, replied to, or unsubscribed. You can also receive aggregate email and SMS campaign completion, SMS lifecycle, subscriber profile and per-list consent, and sequence lifecycle events.

Events

Sequenzy webhook dialog with callouts showing destination and lifecycle events
Supported event types:
email.bounced and email.failed mean different things and you should handle them differently. email.bounced is the receiving server rejecting the message. Its bounce.category says which kind: hard means the address is bad and is suppressed, soft is a temporary problem such as a full inbox or a failed forward, and blocked means the server refused this email over content, policy or reputation while the address is fine. Suppress on hard only. Events sent before this field was added have no category; use bounce.type for those, where Permanent and Undetermined are hard. email.failed means we couldn’t deliver the message and the receiver never judged the mailbox - the address stays subscribed and will be tried again on your next send, so do not suppress on it. Its payload carries a failure object with a code of either transport_exhausted or admin_bounce. The bounce, failure and delivery_delay objects also carry outcome and reason. outcome is bounced when the address can’t take mail, blocked when the receiver refused because of the sender (spam or content, the sending domain, policy, or its SPF, DKIM or DMARC), delayed while delivery is still retried, and failed when we couldn’t deliver it. reason is one sentence, with the receiver’s status code when there is one. diagnostic_code holds the receiver’s reply with our servers masked for bounced and blocked emails, the same sentence as reason for failed ones, and is empty for delays you didn’t cause. Payloads never include our sending IPs. New webhook endpoints subscribe to sent, delivered, delayed, bounced, failed, complained, email unsubscribed, SMS lifecycle (sms.*), invalid subscriber, subscriber created, and subscriber unsubscribed events by default. Add campaign.sent, email.opened, email.clicked, email.replied, email.received, subscriber.updated, subscriber.list_subscribed, subscriber.list_unsubscribed, subscriber_import.completed, sequence.finished, sequence.failed, and poll.answered explicitly if you want aggregate campaign completion, high-volume engagement, inbound reply, new inbox mail, profile sync, per-list consent sync, import completion, sequence lifecycle, or poll answer events. campaign.sent is opt-in. poll.answered payloads include a poll object with the block id, question, answer, stored value, and attribute key.

Payload

Use id to deduplicate events. Webhook payloads use one canonical snake_case field per value. external_id is included when the recipient is linked to a subscriber with a customer-owned external ID, or when a single-recipient transactional send included subscriberExternalId even if no subscriber exists. campaign.sent fires once when an email or SMS campaign reaches its terminal sent state, including sends with no recipients and completed email A/B tests. It is one aggregate event per campaign delivery generation, not one event per recipient. Retries and resumed finalization use the same deterministic event ID for that generation.
campaign_type is email, sms, or push. For push campaigns, total_sent counts contacts whose notification at least one push service accepted and total_failed counts contacts whose every device rejected it. SMS events fire when an SMS sequence step is accepted by the carrier network (sms.sent), confirmed delivered (sms.delivered), rejected or undeliverable (sms.failed), or when a recipient replies STOP (sms.opted_out). Test sends do not emit webhooks. An opt-out applies to every subscriber sharing the phone number; opted_out_subscriber_ids lists all of them.
Inbound reply events include the stored reply, conversation, original send context, body text, HTML body, and stripped text. Attachment bodies are not included; only metadata is delivered.
email.received fires when new mail reaches your Inbox at your inbox address or at any address on a receiving domain. Replies to emails Sequenzy sent fire email.replied instead. Mail the inbound spam or virus scan flags, and mail that joins a conversation you moved to Spam, stays in the Spam folder and fires no webhook. The event is not in the default set; add it to events to receive it. The payload fields:
  • inbound_email_id: the stored message.
  • conversation_id and conversation_message_id: the conversation it opened or joined, and its message in that conversation (null when unavailable).
  • message_id and in_reply_to: the email’s Message-ID and In-Reply-To headers, or null.
  • subscriber_id: the subscriber whose address matches the sender, or null. Mail that fails its DMARC check is never linked to a subscriber.
  • from_email (lowercase) and from_name: the sender.
  • to: the address the email was sent to, such as sales@acme.com, or null when unknown.
  • sender_authenticated: false when the email failed DMARC, so from_email may be forged. Don’t act on the sender’s identity without checking it.
  • subject, body_text, body_html and stripped_text (the new text without quoted history), each null when missing.
  • has_attachments, attachment_count and attachments: attachment metadata only (filename, content_type, size in bytes), never file contents.
  • received_at: when the email was received.
  • metadata.source: ses_inbound.
The event id is derived from the stored message, so a repeated delivery of the same event has the same id; use it to ignore duplicates. Subscriber events include the current subscriber profile when one exists. subscriber.created fires once per newly created contact and carries source (the origin, using the same vocabulary as the per-list consent events) plus list_ids for the lists it joined at creation. It fires for every creation path, including bulk imports, integration syncs, and contacts created as a side effect of another call: an event or inbound webhook for a new address, a tag change, a historical event import (source import, backdated to the earliest event), or a transactional send (source transactional). Contacts created by an inbound webhook carry source inbound_webhook, so a workspace that relays contacts to another Sequenzy workspace can ignore the copies that come back. Filter on source if you only want organic signups. When double opt-in is on, it fires at confirmation rather than at signup, with source set to double_opt_in - a contact who never confirms never emits it. subscriber.invalid fires when a subscriber add attempt cannot create a sendable subscriber because the attempted email is missing, syntactically invalid, has an invalid/blocked domain, or is already suppressed from previous delivery failures. subscriber.updated fires when email, external_id, first_name, last_name, timezone, custom_attributes, or a non-unsubscribe status change occurs. Active to unsubscribed changes emit subscriber.unsubscribed; they only also emit subscriber.updated when another profile field changes in the same update. List and tag changes do not emit subscriber.updated. Use subscriber.list_subscribed and subscriber.list_unsubscribed for per-list consent sync. They fire only when list membership actually changes, so retrying an already-applied add or remove does not create another event. The payload identifies the subscriber and list, reports action as added or removed, and includes a stable source such as preferences_page, dashboard, api, automation, import, double_opt_in, or an integration name such as stripe. A global opt-out continues to emit subscriber.unsubscribed without fanning out one event per list.
Sequence lifecycle payloads include the subscriber email, external ID when available, and the underlying sequence event data:
subscriber_import.completed has one stable logical event ID when a bulk subscriber import reaches its terminal state, carrying the final reconciled counts - added_count + updated_count + skipped_count + failed_count equals total_rows, with per-reason breakdowns in skipped_reasons and failed_reasons. Transient enqueue failures are repaired automatically. HTTP delivery can retry, so deduplicate by the top-level event id as with every webhook:

Signatures

Every request includes:
To verify a request, compute an HMAC-SHA256 digest over:
Use an active webhook signing secret returned when you create the webhook or add a signing secret. Compare the result to any v1= value in X-Sequenzy-Signature. If a webhook has multiple active signing secrets, Sequenzy still sends one POST request and includes one signature per secret in the same header:
To change secrets without downtime, add a new secret, deploy it in your receiver, then remove the old secret.

Delivery

Your endpoint must use HTTPS and return a 2xx status within 4 seconds. Failed requests receive up to 16 delivery attempts with exponential backoff spanning 7 days. Delivery rows keep the latest status, error, and failed-response snippet. You can replay a delivery manually from the API. Sequenzy connects only to public HTTPS targets. Delivery workers resolve and validate the hostname before each attempt, then connect to that vetted address while preserving the original host for TLS and request verification. If an endpoint repeatedly fails, delivery pauses for 10 minutes for that endpoint so other webhook endpoints keep moving. After the pause, Sequenzy sends one pending delivery to test the endpoint and releases the rest only once it succeeds. Successful delivery resets the endpoint failure counter. Editing the endpoint URL, re-enabling the endpoint, sending a test event, or replaying a delivery also resets the stored failure state so you can verify a fixed receiver immediately. If every delivery to an endpoint fails for 7 days, Sequenzy disables the endpoint, skips its pending deliveries, and emails the workspace owner. The endpoint shows as disabled automatically in Webhooks, and the API returns the time in autoDisabledAt. Once your receiver returns 2xx again, enable the endpoint to receive new events. Create and manage endpoints with the webhooks API.