> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sequenzy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Outbound Webhooks

> Send email, subscriber, and sequence lifecycle events from Sequenzy to your own endpoint.

# Outbound Webhooks

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 subscriber profile events and sequence lifecycle events.

## Events

Supported event types:

```json theme={null}
[
  "email.sent",
  "email.delivered",
  "email.delivery_delayed",
  "email.bounced",
  "email.complained",
  "email.opened",
  "email.clicked",
  "email.replied",
  "email.unsubscribed",
  "subscriber.invalid",
  "subscriber.updated",
  "subscriber.unsubscribed",
  "sequence.finished",
  "sequence.failed"
]
```

New webhook endpoints subscribe to sent, delivered, delayed, bounced, complained, email unsubscribed, invalid subscriber, and subscriber unsubscribed events by default. Add `email.opened`, `email.clicked`, `email.replied`, `subscriber.updated`, `sequence.finished`, and `sequence.failed` explicitly if you want high-volume engagement, inbound reply, profile sync, or sequence lifecycle events.

## Payload

```json theme={null}
{
  "id": "evt_123",
  "type": "email.delivered",
  "object": "event",
  "metric": "delivered",
  "created_at": "2026-05-05T12:00:00.000Z",
  "data": {
    "email_send_id": "send_123",
    "message_id": "ses-message-id",
    "campaign_id": "camp_123",
    "subscriber_id": "sub_123",
    "external_id": "customer_123",
    "recipient": "user@example.com",
    "subject": "Welcome",
    "email_type": "campaign",
    "metadata": {
      "source": "campaign"
    }
  }
}
```

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.

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.

```json theme={null}
{
  "id": "evt_reply",
  "type": "email.replied",
  "object": "event",
  "metric": "replied",
  "created_at": "2026-05-05T12:00:00.000Z",
  "data": {
    "reply_id": "reply_123",
    "conversation_id": "conv_123",
    "conversation_message_id": "msg_123",
    "email_send_id": "send_123",
    "message_id": "reply-message-id",
    "in_reply_to": "ses-message-id",
    "campaign_id": "camp_123",
    "subscriber_id": "sub_123",
    "external_id": "customer_123",
    "from_email": "user@example.com",
    "from_name": "Ada",
    "recipient": "user@example.com",
    "subject": "Re: Welcome",
    "email_type": "campaign",
    "body_text": "Thanks for the update.",
    "body_html": "<p>Thanks for the update.</p>",
    "stripped_text": "Thanks for the update.",
    "has_attachments": false,
    "attachment_count": 0,
    "received_at": "2026-05-05T12:00:00.000Z"
  }
}
```

Subscriber events include the current subscriber profile when one exists. `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`, `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`.

```json theme={null}
{
  "id": "evt_invalid",
  "type": "subscriber.invalid",
  "object": "event",
  "metric": "invalid",
  "created_at": "2026-05-05T12:00:00.000Z",
  "data": {
    "email": "bad@gmai.com",
    "external_id": "customer_123",
    "first_name": "Ada",
    "custom_attributes": {
      "plan": "pro"
    },
    "reason_code": "invalid_email_domain",
    "reason": "Invalid domain for \"bad@gmai.com\": Domain is blacklisted (typosquatting or disposable email)",
    "metadata": {
      "source": "add_subscriber"
    }
  }
}
```

```json theme={null}
{
  "id": "evt_789",
  "type": "subscriber.updated",
  "object": "event",
  "metric": "updated",
  "created_at": "2026-05-05T12:00:00.000Z",
  "data": {
    "subscriber_id": "sub_123",
    "external_id": "customer_new",
    "email": "new@example.com",
    "first_name": "Ada",
    "last_name": "Lovelace",
    "status": "active",
    "custom_attributes": {
      "plan": "pro"
    },
    "changed_fields": ["email", "external_id", "custom_attributes"],
    "previous": {
      "email": "old@example.com",
      "external_id": "customer_old",
      "custom_attributes": {
        "plan": "starter"
      }
    }
  }
}
```

Sequence lifecycle payloads include the subscriber email, external ID when available, and the underlying sequence event data:

```json theme={null}
{
  "id": "evt_456",
  "type": "sequence.failed",
  "object": "event",
  "metric": "failed",
  "created_at": "2026-05-05T12:00:00.000Z",
  "data": {
    "sequence_id": "seq_welcome",
    "sequence_name": "Welcome sequence",
    "automation_id": "seq_welcome",
    "automation_name": "Welcome sequence",
    "automation_token_id": "token_123",
    "token_id": "token_123",
    "subscriber_id": "sub_123",
    "external_id": "customer_123",
    "email": "user@example.com",
    "lifecycle": "failed",
    "status": "failed",
    "current_node_id": "node_email_2",
    "reason": "Email template email_123 not found",
    "event": {
      "id": "event_123",
      "name": "sequence_failed",
      "properties": {
        "sequence_id": "seq_welcome",
        "sequence_name": "Welcome sequence",
        "automation_id": "seq_welcome",
        "automation_name": "Welcome sequence",
        "automation_token_id": "token_123",
        "token_id": "token_123",
        "lifecycle": "failed",
        "status": "failed",
        "current_node_id": "node_email_2",
        "reason": "Email template email_123 not found"
      }
    }
  }
}
```

## Signatures

Every request includes:

```txt theme={null}
X-Sequenzy-Event-Id: evt_123
X-Sequenzy-Event-Type: email.delivered
X-Sequenzy-Timestamp: 1777977600
X-Sequenzy-Signature: v1=...
```

To verify a request, compute an HMAC-SHA256 digest over:

```txt theme={null}
v1:{timestamp}:{raw_request_body}
```

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:

```txt theme={null}
X-Sequenzy-Signature: v1=abc123,v1=def456
```

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 are retried with exponential backoff for up to 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 briefly for that endpoint so other webhook endpoints keep moving. 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.

Create and manage endpoints with the [webhooks API](../api-reference/webhooks/outbound).
