Skip to main content
POST
Outbound Webhooks
You can configure an endpoint to receive Sequenzy campaign, email, SMS, subscriber, and sequence lifecycle events as signed JSON POST requests.

Create

string
required
Human-readable webhook name.
string
required
Absolute HTTPS endpoint URL.
string[]
Event types to receive. Omit this field to receive the default lifecycle events: sent, delivered, delayed, bounced, complained, email unsubscribed, SMS lifecycle (sms.sent, sms.delivered, sms.failed, sms.opted_out), invalid subscriber, subscriber created, and subscriber unsubscribed. Add opened, campaign.sent, clicked, replied, email.received, subscriber.updated, subscriber.list_subscribed, subscriber.list_unsubscribed, poll.answered, subscriber_import.completed, sequence.finished, and sequence.failed explicitly if you need aggregate campaign completion, engagement, inbound reply, new inbox mail, poll response, profile sync, per-list consent sync, import completion, or sequence lifecycle events. campaign.sent is not in the default set.

Manage

Updating an endpoint URL, enabling a disabled endpoint, sending a test event, and replaying a delivery reset the endpoint failure state so a recovered receiver can be tested immediately. failingSince is when the current run of failed deliveries started, and it is null once a delivery succeeds. If every delivery fails for 7 days, Sequenzy disables the endpoint, skips its pending deliveries, emails the workspace owner, and sets autoDisabledAt. Enabling the endpoint or changing its URL clears both fields. A test event targets the endpoint you name, so it is delivered even when that endpoint subscribes to no event types. The response returns the queued delivery, so you can poll that delivery ID for the result instead of waiting for it to show up in the delivery list:

Event Payload

Each webhook delivery sends a signed JSON event to your endpoint. Email events include Sequenzy IDs and, when available, your subscriber external ID or the subscriberExternalId stored on a transactional send:
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. email.sent events include computed_lists when campaign personalization selected per-recipient list items for that email; items preserves the original campaign data objects. campaign.sent is one aggregate event when an email or SMS campaign reaches sent, including sends with no recipients and completed email A/B tests. It does not fire per recipient. The ID is deterministic for the campaign delivery generation, so a retried or resumed finalization cannot create duplicate deliveries.
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. poll.answered fires after a Poll or NPS response is stored. For single-answer polls, the nested poll.value is the stable scalar value saved to the subscriber attribute, while poll.answer is the human-readable label captured for reporting. For multi-select polls, poll.value is the complete selected-value array; poll.values and poll.answers provide parallel value and label arrays, and poll.allow_multiple is true.
A multi-select response uses this poll shape:
email.replied fires when an inbound reply is stored. The payload includes reply, conversation, and original send context plus the reply 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. subscriber.list_subscribed and subscriber.list_unsubscribed fire only for actual per-list membership transitions. Each payload includes the subscriber ID, email, optional external ID, list ID and name, action (added or removed), and the change source. A global opt-out emits subscriber.unsubscribed without a per-list event fan-out.
Sequence lifecycle events include the subscriber email, external ID when available, and the event data recorded for that sequence enrollment:

Responses

Verify Requests

Webhook requests include X-Sequenzy-Timestamp and X-Sequenzy-Signature. Build the signed payload as v1:{timestamp}:{raw_request_body}, compute an HMAC-SHA256 digest with each active webhook signing secret, and compare it to any v1= signature value. If a webhook has multiple active signing secrets, Sequenzy still sends one POST request and includes one v1= signature per secret in the same header. Add a new secret, deploy it in your receiver, then remove the old secret. Return a 2xx status within 4 seconds. Failed deliveries receive up to 16 attempts with exponential backoff spanning 7 days.