> ## 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.

# contact.unsubscribed Webhook Event

> The contact.unsubscribed webhook fires when an AhaSend contact unsubscribes from a list or from all lists, for example through a campaign's link.

AhaSend sends this event when a membership's subscription status becomes `unsubscribed`, or when the contact's unsubscribe from all lists is set (`unsubscribed` changes to `true`).

A recipient who unsubscribes from a campaign email sends this event with `origin: recipient` and the `data.campaign_id` of the campaign. The campaign email also sends its own [`message.unsubscribed`](https://ahasend.com/docs/api-reference/webhooks/message-unsubscribed). For a membership, `data.previous_subscription_status` is the status before the change. For the unsubscribe from all lists, `data.list` and `data.list_contact` are `null`.

A recipient of a campaign sent to a segment unsubscribes from all lists, so the event has `data.list` and `data.list_contact` set to `null`, and `data.segment_id` is the segment.

**No event**:

* An unsubscribe of a membership or a contact that is already unsubscribed
* A change of a membership's status to `unconfirmed`
* A new membership, which sends `contact.added_to_list`, and a new contact, which sends `contact.created`

**Setting**: `on_contact_unsubscribed` (Contact Unsubscribed in the dashboard).

**Requirements**:

* Your account has the marketing features. Contact events cannot be turned on without them.
* A webhook with this event on needs an API key with `contacts:read` and `lists:read` when you create or update it through the API. See [contact events](https://ahasend.com/docs/api-reference/webhooks.md#contact-events).

**When it's sent**: When a contact unsubscribes from a list, or from all lists.

## 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).
* [Event reference](https://ahasend.com/docs/api-reference/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), [Contacts API](https://ahasend.com/docs/api-reference/contacts/overview.md), [Lists API](https://ahasend.com/docs/api-reference/lists/overview.md).


## OpenAPI

````yaml webhooks.yaml webhook contact.unsubscribed
openapi: 3.1.0
info:
  title: AhaSend Webhooks
  description: >
    AhaSend webhook events documentation. This specification describes all
    webhook events that AhaSend sends to your configured webhook URLs.


    ## Overview


    Webhooks are HTTP callbacks that AhaSend sends to your configured URLs when
    specific events occur. They provide real-time notifications about:


    - **Message Events**: Email delivery status (sent, delivered, bounced,
    opened, unsubscribed, reported as spam, etc.)

    - **Suppression Events**: When a suppression is created or deleted, by a
    recipient, by AhaSend, or by you

    - **Domain Events**: DNS configuration issues

    - **Contact Events**: Changes to your contacts and their list memberships,
    such as a new contact, an unsubscribe, or a spam complaint

    - **Route Events**: Inbound email processing


    ## Standard Webhooks Compatibility


    AhaSend webhook deliveries use the Standard Webhooks header names,
    signed-content format, HMAC-SHA256 algorithm, and signature format. Secret
    handling is intentionally different from the encoded-secret convention
    assumed by some Standard Webhooks libraries, so compatibility with stock
    libraries is not unconditional.


    ### Security Headers


    All webhooks include these security headers for verification:


    - `webhook-id`: Unique identifier for the webhook event (used as idempotency
    key)

    - `webhook-timestamp`: Unix timestamp when the webhook was sent

    - `webhook-signature`: HMAC-SHA256 signature of the payload using the
    resource secret, written as `v1,` followed by the signature in standard
    Base64


    ### Verification


    The HMAC key is the literal UTF-8 bytes of the `secret` returned when the
    webhook or route resource is created. Use the complete returned string,
    including any prefix. Do not Base64-decode it and do not strip a prefix.


    Use the AhaSend SDK verifier, or a verifier that explicitly accepts raw key
    bytes. A stock Standard Webhooks library is compatible only if it has a
    raw-secret/raw-key mode that preserves these literal UTF-8 bytes.
    Constructors that decode an encoded Standard Webhooks secret will derive a
    different key and reject valid AhaSend deliveries.


    The signed content is the unmodified `webhook-id`, `webhook-timestamp`, and
    raw request body joined with periods. See the [Standard Webhooks
    verification
    algorithm](https://github.com/standard-webhooks/standard-webhooks/blob/main/spec/standard-webhooks.md#verifying-webhook-authenticity)
    for the shared protocol details, subject to the AhaSend secret-handling
    boundary above.


    ### Retry Policy


    - Each webhook delivery is tried up to **6 times** in all (the first try and
    5 retries), over about **16 minutes**

    - Each route delivery is tried up to **12 times** in all, over about **11
    hours**

    - Only HTTP status codes **200-299** are considered successful

    - When more than **100 attempts in a row** fail, retries included, the
    webhook or route is automatically disabled

    - You'll receive an email notification when a webhook or route is disabled


    ### Payload Format


    All webhooks follow the Standard Webhooks payload structure:


    ```json

    {
      "type": "event.type",
      "webhook_id": "abe11757-2886-4b55-96f1-0e0afc95795a",
      "timestamp": "2024-05-06T09:49:16.687031577Z",
      "data": {
        // Event-specific data
      }
    }

    ```


    Message, suppression, domain, and contact webhooks use `webhook_id`. Route

    webhooks use `route_id` instead.


    ### Event Order


    Events are not delivered in the order in which they happened. Many workers

    send them, and each delivery is retried on its own, so 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.


    ### 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`.


    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 are available to accounts with the marketing features. They

    report changes to your contacts and to their list memberships (list

    contacts). 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`.


    A webhook scoped 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 global webhooks.


    A contact event is sent only for a real change. 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: one for each membership that

    it adds, removes, or changes the status of, one `contact.updated` for the

    contact's fields, and one for the contact's unsubscribe from all lists.

    There is one exception: a spam complaint about a campaign sent to a

    segment sends `contact.complained` when AhaSend records the complaint for

    the campaign's recipient, whatever the contact's state.


    **Unsubscribe from all lists**: The contact's `unsubscribed` field

    unsubscribes the contact from all lists. When it changes from `false` to

    `true`, AhaSend sends `contact.unsubscribed`, or `contact.complained` when

    a spam complaint about a campaign sent to a segment changes it. When it

    changes from `true` to `false`, AhaSend sends `contact.subscribed`. These

    events are not about one list, so their `list` and `list_contact` are

    `null`. A contact that is created with `unsubscribed: true` sends only

    `contact.created`.


    **Campaigns sent to a segment**: A segment is not a list, so a recipient

    of a campaign sent to a segment who unsubscribes or reports the email as

    spam unsubscribes from all lists. The `contact.unsubscribed` or

    `contact.complained` event has `list` and `list_contact` set to `null`,

    and carries the `campaign_id` and the `segment_id`.


    **What sends no contact 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 (`validation_status` and `last_validated_at`). 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


    ### 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


    Message events and `domain.dns_error` have no `origin`: a recipient, a mail

    server, or a DNS check always causes them.


    **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` (message events and `domain.dns_error`)

    always pass the filter. In the dashboard, the setting is "Send changes

    made by".


    **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

    set the webhook's `origins` to leave out `api`, so that it does not get the

    changes made through the API.


    ## Getting Started


    1. **Configure a webhook** in your AhaSend dashboard

    2. **Choose which events** you want to receive

    3. **Verify webhook signatures** using the literal resource secret as
    described above

    4. **Handle the events** in your application


    For more information, visit the [AhaSend webhook
    documentation](https://ahasend.com/help/integrations/webhooks).
  version: 2.0.0
  contact:
    email: support@ahasend.com
  license:
    name: Proprietary
servers:
  - url: https://your-webhook-endpoint.com
    description: Your webhook endpoint URL (configured in AhaSend dashboard)
security: []
tags:
  - name: Message Events
    description: >
      Webhooks for outbound email delivery tracking.


      These events track the lifecycle of emails you send through AhaSend, from
      initial reception through final delivery or failure.
  - name: Suppression Events
    description: >
      Webhooks for email suppression list changes.


      These events notify you when a suppression is created, gets a new reason,
      or is deleted: after delivery issues, when a recipient unsubscribes or
      reports spam, and when you or AhaSend support change your suppression
      list. Each event carries its `origin`.
  - name: Domain Events
    description: >
      Webhooks for domain configuration issues.


      These events alert you to DNS configuration problems that could prevent
      email delivery.
  - name: Contact Events
    description: >
      Webhooks for changes to your contacts and their list memberships.


      These events notify you when contacts are created, changed, or deleted,
      when they join or leave lists, and when they subscribe, unsubscribe, or
      report a campaign email as spam. They are available to accounts with the
      marketing features.
  - name: Route Events
    description: >
      Webhooks for inbound email processing.


      These events deliver inbound emails to your application for processing
      (e.g., support tickets, replies).
paths: {}

````

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