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

> ## Agent Instructions
> AhaSend stores all message data in the EU. See /facts.
> The API base URL is https://api.ahasend.com. Send the API key in the Authorization: Bearer header. See /api-reference/authentication.
> Use send.ahasend.com for SMTP. Ports 25, 587 and 2525 use STARTTLS. Port 465 is not supported. See /smtp.
> For send-only integrations, create an API Key v2 under Credentials → Add and scope it to messages:send:{your-domain}. See /send-api/credentials and /api-reference/scopes.

# Webhook Events and Payload Reference

> Reference for AhaSend email webhooks: every event type (delivered, bounced, opened, clicked, failed and more), payload format, signature headers and retries.

AhaSend webhook events report email delivery, engagement, suppression, contact and domain changes; this reference lists every event and its payload. Use the [webhook setup guide](https://ahasend.com/docs/integrations/webhooks.md) to configure an endpoint.

Available on Free, Pro and Max. See [plans and features](https://ahasend.com/docs/facts.md#plans-and-features).

## Event Types

| Event type | Filter flag | Fires when | Payload |
| - | - | - | - |
| `message.reception` | `on_reception` | A message is accepted | [Reference](https://ahasend.com/docs/api-reference/webhooks/message-reception.md) |
| `message.delivered` | `on_delivered` | The recipient server accepts the message | [Reference](https://ahasend.com/docs/api-reference/webhooks/message-delivered.md) |
| `message.transient_error` | `on_transient_error` | A delivery attempt has a temporary failure | [Reference](https://ahasend.com/docs/api-reference/webhooks/message-transient_error.md) |
| `message.failed` | `on_failed` | Message delivery fails | [Reference](https://ahasend.com/docs/api-reference/webhooks/message-failed.md) |
| `message.bounced` | `on_bounced` | The recipient server rejects the message | [Reference](https://ahasend.com/docs/api-reference/webhooks/message-bounced.md) |
| `message.suppressed` | `on_suppressed` | A suppression prevents sending | [Reference](https://ahasend.com/docs/api-reference/webhooks/message-suppressed.md) |
| `message.opened` | `on_opened` | The tracking image is requested | [Reference](https://ahasend.com/docs/api-reference/webhooks/message-opened.md) |
| `message.clicked` | `on_clicked` | A tracked link is requested | [Reference](https://ahasend.com/docs/api-reference/webhooks/message-clicked.md) |
| `message.unsubscribed` | `on_unsubscribed` | A recipient unsubscribes from a campaign email | [Reference](https://ahasend.com/docs/api-reference/webhooks/message-unsubscribed.md) |
| `message.complained` | `on_complained` | A campaign email is reported as spam | [Reference](https://ahasend.com/docs/api-reference/webhooks/message-complained.md) |
| `suppression.created` | `on_suppression_created` | A suppression is created or gets a new reason | [Reference](https://ahasend.com/docs/api-reference/webhooks/suppression-created.md) |
| `suppression.deleted` | `on_suppression_deleted` | A suppression is deleted | [Reference](https://ahasend.com/docs/api-reference/webhooks/suppression-deleted.md) |
| `domain.dns_error` | `on_dns_error` | A sending domain has a DNS error | [Reference](https://ahasend.com/docs/api-reference/webhooks/domain-dns_error.md) |
| `contact.created` | `on_contact_created` | A contact is created | [Reference](https://ahasend.com/docs/api-reference/webhooks/contact-created.md) |
| `contact.updated` | `on_contact_updated` | A field of a contact changes | [Reference](https://ahasend.com/docs/api-reference/webhooks/contact-updated.md) |
| `contact.deleted` | `on_contact_deleted` | A contact is deleted | [Reference](https://ahasend.com/docs/api-reference/webhooks/contact-deleted.md) |
| `contact.added_to_list` | `on_contact_added_to_list` | A contact is added to a list | [Reference](https://ahasend.com/docs/api-reference/webhooks/contact-added_to_list.md) |
| `contact.removed_from_list` | `on_contact_removed_from_list` | A contact is removed from a list | [Reference](https://ahasend.com/docs/api-reference/webhooks/contact-removed_from_list.md) |
| `contact.subscribed` | `on_contact_subscribed` | A list membership is confirmed, or the unsubscribe from all lists is cleared | [Reference](https://ahasend.com/docs/api-reference/webhooks/contact-subscribed.md) |
| `contact.unsubscribed` | `on_contact_unsubscribed` | A contact unsubscribes from a list or from all lists | [Reference](https://ahasend.com/docs/api-reference/webhooks/contact-unsubscribed.md) |
| `contact.complained` | `on_contact_complained` | A contact reports a campaign email as spam | [Reference](https://ahasend.com/docs/api-reference/webhooks/contact-complained.md) |

Inbound routing has a separate [`message.routing` event](https://ahasend.com/docs/api-reference/routes/route-message).

## Suppression Duration

See [bounces and suppression durations](https://ahasend.com/docs/bounces.md#suppression-duration) for expiry rules and the next steps after a failed send.

## Payload Structure

All webhook events use a consistent JSON structure:

Events contain `type`, `webhook_id`, `timestamp`, and an event-specific `data` object. The payload `webhook_id` identifies the configured endpoint; the `webhook-id` header identifies the delivery across retries. Inbound routes use `route_id` instead of `webhook_id`. Use the payload reference for each event above.

## Message Events

Transactional and campaign messages send the same message events with the same payload. The events of a campaign message also carry `campaign_id`, `list_id` and `segment_id`. `message.unsubscribed` and `message.complained` are sent for campaign email only.

AhaSend reads the message when it sends a message event. When the message is deleted before then, or for a campaign message its campaign recipient or its contact, the event is not sent. An event of a campaign message is also not sent when it happens before AhaSend has logged that it received the message.

## Contact Events

Contact events report changes to your contacts and to their list memberships. They are available to accounts with the marketing features. Every contact event carries the `contact` and its `origin`. The events that can be about a list membership (`contact.added_to_list`, `contact.removed_from_list`, `contact.subscribed`, `contact.unsubscribed` and `contact.complained`) also carry the `list` and the `list_contact`.

* **Real changes only**: A write that leaves the contact and its memberships as they were sends nothing. One write sends one event for each kind of change that it makes. A spam complaint about a campaign sent to a segment is the exception: it sends `contact.complained` whatever the contact's state.
* **Unsubscribe from all lists**: The contact's `unsubscribed` field unsubscribes the contact from all lists. When it changes to `true`, AhaSend sends `contact.unsubscribed`, or `contact.complained` for a spam complaint about a campaign sent to a segment. When it changes to `false`, AhaSend sends `contact.subscribed`. These events are not about one list, so their `list` and `list_contact` are `null`. A recipient of a campaign sent to a segment who unsubscribes or reports spam unsubscribes from all lists, and the event also carries `campaign_id` and `segment_id`.
* **Domains**: A webhook limited to some of your domains gets a contact event only when an email from one of those domains caused it: an unsubscribe, a complaint, or a bounce that disables the contact. A change made through the API or the dashboard, or by email validation, has no domain and goes only to webhooks for all domains.
* **API key scopes**: Through the API, a webhook with any contact event on needs an API key with `contacts:read`. The list events also need `lists:read`. Without them, [Create Webhook](https://ahasend.com/docs/api-reference/webhooks/create-webhook.md) and [Update Webhook](https://ahasend.com/docs/api-reference/webhooks/update-webhook.md) answer `403`. Turning a contact event on for an account without the marketing features answers `400`. A key without these scopes can still turn the contact events off or disable the webhook.

## Origin

Contact and suppression events carry `origin`, which says who made the change:

* `recipient`: the contact or recipient, through an unsubscribe link, a one-click unsubscribe, or a spam complaint
* `api`: you, through the AhaSend API
* `dashboard`: you, through the AhaSend dashboard
* `system`: AhaSend, for example after a bounce, an expired message or an email validation, or when AhaSend support makes the change

AhaSend can add an origin, so keep a fallback for a value you do not know. Message events and `domain.dns_error` have no `origin`.

**Origin filter**: Each webhook has an `origins` setting, the list of origins whose contact and suppression events it gets. The default is `null`, which means every origin. A list that holds all four origins is saved as `null`, so that an origin added later reaches the webhook too. An empty list is refused: to stop contact or suppression events, turn them off. Events without an `origin` always pass the filter. In the dashboard, the setting is **Send changes made by**.

> [!WARNING]
>
> **Prevent loops**: A handler that writes to the AhaSend API every time it receives `contact.updated` receives its own write as a new `contact.updated` event, with `origin: api`. Make sure that the handler does not write again when the contact already has the values it wants, or leave `api` out of the webhook's `origins`.

## Event Order

Events are not delivered in the order in which they happened, and a later event can arrive first. Order the events by their `timestamp`. For a contact or a suppression event it is the time of the change; for `message.unsubscribed` and `message.complained`, the time of the unsubscribe request or of the spam complaint report.

## Changes That Send No Event

* A change of a membership's status to `unconfirmed`
* Importing contacts from a CSV file
* Saving a contact search as a list
* Removing a contact field from all contacts
* Email validation results. A validation that blocks a contact changes its `status`, and that sends `contact.updated`.
* The memberships that are deleted with their contact: `contact.deleted` says that the contact and all its memberships are gone
* Deleting a list: the list's memberships are kept
* A repeat of an existing suppression that does not change its reason
* [Wiping suppressions](https://ahasend.com/docs/api-reference/suppressions/wipe-suppressions.md). After a wipe, read the list again with [List Suppressions](https://ahasend.com/docs/api-reference/suppressions/get-suppressions.md).
* The removal of expired suppressions: `suppression.created` already gave the `expires_at`

## Delivery and Verification

Check [signatures](https://ahasend.com/docs/api-reference/webhooks/security.md) before trusting a payload, and use the [retry policy](https://ahasend.com/docs/api-reference/webhooks/retry-policy.md) to handle duplicate or failed deliveries. Manage endpoints with [Create Webhook](https://ahasend.com/docs/api-reference/webhooks/create-webhook.md), [List Webhooks](https://ahasend.com/docs/api-reference/webhooks/get-webhooks.md), [Update Webhook](https://ahasend.com/docs/api-reference/webhooks/update-webhook.md) and [Delete Webhook](https://ahasend.com/docs/api-reference/webhooks/delete-webhook.md).

## Related Guides

* [API authentication](https://ahasend.com/docs/api-reference/authentication.md), [Permission scopes](https://ahasend.com/docs/api-reference/scopes.md), [Webhook setup](https://ahasend.com/docs/integrations/webhooks.md).
* [Webhook CLI](https://ahasend.com/docs/cli/commands/webhooks.md), [Local webhook testing](https://ahasend.com/docs/cli/webhook-testing.md), [Signature verification](https://ahasend.com/docs/api-reference/webhooks/security.md).
* [Retry policy](https://ahasend.com/docs/api-reference/webhooks/retry-policy.md), [API errors](https://ahasend.com/docs/api-reference/errors.md), [API rate limits](https://ahasend.com/docs/api-reference/rate-limits.md).
* [Tracking setup](https://ahasend.com/docs/tracking/open-tracking.md), [Contacts API](https://ahasend.com/docs/api-reference/contacts/overview.md), [Lists API](https://ahasend.com/docs/api-reference/lists/overview.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.