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

# suppression.created Webhook Event

> The suppression.created webhook fires when AhaSend suppresses an address, for example after a hard bounce, or when a suppression gets a new reason.

AhaSend sends this event when a suppression is created for an email address, or when an existing suppression gets a new reason.

**Why suppressions are created**:

* A hard bounce from the email address (`origin: system`)
* A message to the address that fails with temporary errors on several delivery attempts until it expires (`origin: system`)
* The recipient reports a transactional email as spam (reason `FBL Complaint Report`, `origin: recipient`)
* The recipient unsubscribes through the unsubscribe link of a transactional email (reason `Unsubscribed`, `origin: recipient`)
* You create a suppression through the API (`origin: api`). A request without a domain creates a suppression on each domain of your account, and sends one event for each.

A bounce or an expired message does not suppress the address when the receiving server rejected the message for policy reasons (for example a blocklist or a spam filter), when the failure was on AhaSend's side, or, for transactional email, when a later message to the address was delivered.

An unsubscribe from a campaign email or a spam complaint about one creates no suppression. It sends [contact events](https://ahasend.com/docs/api-reference/webhooks.md#contact-events) instead.

**Expiration**: A suppression after delivery errors expires after at most 30 days, depending on the error. A suppression after an unsubscribe or a spam complaint is effectively permanent. A suppression made through the API expires at the `expires_at` of its request. See [suppression durations](https://ahasend.com/docs/bounces.md#suppression-duration).

**Impact**: Future emails to this address will be automatically suppressed until the suppression expires or is manually removed.

`data` is the suppression as it was right after the change, and `timestamp` is the time of the change. For a suppression that got a new reason, `data.created_at` stays the time the suppression was first created, so store suppressions by `data.recipient` and `data.sending_domain`.

**No event**:

* A repeat of an existing suppression, such as a second bounce or a second click on the same unsubscribe link, even when it moves the expiry later
* The removal of expired suppressions. `data.expires_at` already says when the suppression ends.

**Setting**: `on_suppression_created` (Suppression Created in the dashboard).

**When it's sent**: When a new suppression is added to your account, or when an existing suppression gets a new reason, for example when a recipient unsubscribes after a bounce.

## 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), [Suppression durations](https://ahasend.com/docs/bounces.md#suppression-duration), [Suppression Deleted](https://ahasend.com/docs/api-reference/webhooks/suppression-deleted.md).


## OpenAPI

````yaml webhooks.yaml webhook suppression.created
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.