AhaSend · AsyncAPI Specification

AhaSend Webhooks

Version 2.0.0

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, etc.) - **Suppression Events**: When email addresses are automatically suppressed - **Domain Events**: DNS configuration issues - **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 ### 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 - Failed webhooks are retried **6 times** over **16+ minutes** - Only HTTP status codes **200-299** are considered successful - After **100 consecutive failures**, the webhook is automatically disabled - You'll receive an email notification when a webhook 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, and domain webhooks use `webhook_id`. Route webhooks use `route_id` instead. ## 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).

View Spec View on GitHub EmailTransactional EmailDeveloper ToolsSMTPWebhookAsyncAPIEventsWebhooks

Servers

https
your-webhook-endpoint.com https://your-webhook-endpoint.com
Your webhook endpoint URL (configured in AhaSend dashboard)

AsyncAPI Specification

Raw ↑
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, etc.)
    - **Suppression Events**: When email addresses are automatically suppressed
    - **Domain Events**: DNS configuration issues
    - **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

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

    - Failed webhooks are retried **6 times** over **16+ minutes**
    - Only HTTP status codes **200-299** are considered successful
    - After **100 consecutive failures**, the webhook is automatically disabled
    - You'll receive an email notification when a webhook 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, and domain webhooks use `webhook_id`. Route
    webhooks use `route_id` instead.

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

webhooks:
  # Message Event Webhooks
  message.reception:
    post:
      summary: Message Reception Event
      description: |
         

        Triggered when an email has been received and queued.
        This is always the first event in a message's lifecycle. The message has been accepted by AhaSend and is queued for delivery to the recipient's mail server.

        **When it's sent**: Immediately after AhaSend receives and queues an email for delivery.
        <Note>
          This webhook is called for both inbound and outbound emails.
        </Note>
      tags:
        - Message Events
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageReceptionWebhookPayload'
            example:
              type: "message.reception"
              webhook_id: "abe11757-2886-4b55-96f1-0e0afc95795a"
              timestamp: "2024-05-06T09:49:16.687031577Z"
              data:
                account_id: "4cdd7bdd-294e-4762-892f-83d40abf5a87"
                event: "on_reception"
                from: "sender@example.com"
                recipient: "recipient@example.com"
                subject: "Welcome to our service"
                message_id_header: "<message-id-12345@localhost>"
                id: "407926766d2711f09b30960002cafe7c"
      responses:
        '200':
          description: Webhook received successfully
        '201':
          description: Webhook received successfully
        '202':
          description: Webhook received successfully
        '204':
          description: Webhook received successfully

  message.delivered:
    post:
      summary: Message Delivered Event
      description: |
        &nbsp;

        Triggered when an email has been successfully delivered to the recipient's mail server.

        This indicates that the recipient's mail server has accepted the email. Note that this doesn't guarantee the email reached the recipient's inbox (it could still be filtered to spam).

        **When it's sent**: When the recipient's mail server confirms successful delivery.
      tags:
        - Message Events
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageDeliveredWebhookPayload'
            example:
              type: "message.delivered"
              webhook_id: "abe11757-2886-4b55-96f1-0e0afc95795a"
              timestamp: "2024-05-06T09:50:16.687031577Z"
              data:
                account_id: "4cdd7bdd-294e-4762-892f-83d40abf5a87"
                event: "on_delivered"
                from: "sender@example.com"
                recipient: "recipient@example.com"
                subject: "Welcome to our service"
                message_id_header: "<message-id-12345@localhost>"
                id: "407926766d2711f09b30960002cafe7c"
                delivery_attempt:
                  smtp_code: 250
                  enhanced_status_code: "2.0.0"
                  response: "OK: queued"
                  command: "DATA"
      responses:
        '200':
          description: Webhook received successfully
        '201':
          description: Webhook received successfully
        '202':
          description: Webhook received successfully
        '204':
          description: Webhook received successfully

  message.transient_error:
    post:
      summary: Message Transient Error Event
      description: |
        &nbsp;

        Triggered when an email delivery is delayed due to a temporary issue with the receiving mail server.

        This is a temporary failure (soft bounce). AhaSend will retry delivery according to its retry policy. Common causes include:
        - Recipient's mailbox is full
        - Temporary server issues
        - Rate limiting by the recipient's server

        **When it's sent**: When a temporary delivery failure occurs.
      tags:
        - Message Events
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageTransientErrorWebhookPayload'
            example:
              type: "message.transient_error"
              webhook_id: "abe11757-2886-4b55-96f1-0e0afc95795a"
              timestamp: "2024-05-06T09:51:16.687031577Z"
              data:
                account_id: "4cdd7bdd-294e-4762-892f-83d40abf5a87"
                event: "on_transient_error"
                from: "sender@example.com"
                recipient: "recipient@example.com"
                subject: "Welcome to our service"
                message_id_header: "<message-id-12345@localhost>"
                id: "407926766d2711f09b30960002cafe7c"
                delivery_attempt:
                  classification: "QuotaIssues"
                  smtp_code: 452
                  enhanced_status_code: "4.2.2"
                  response: "The recipient's inbox is out of storage space"
                  command: "RCPT TO"
      responses:
        '200':
          description: Webhook received successfully
        '201':
          description: Webhook received successfully
        '202':
          description: Webhook received successfully
        '204':
          description: Webhook received successfully

  message.failed:
    post:
      summary: Message Failed Event
      description: |
        &nbsp;

        Triggered when an email exhausts its retry window without being delivered.

        No more delivery attempts will be made. This event reports retry exhaustion rather than one specific SMTP exchange, so `data.delivery_attempt` is omitted. An immediate permanent rejection from a recipient server is reported as `message.bounced` instead.

        **When it's sent**: After all retry attempts have been exhausted.
      tags:
        - Message Events
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageFailedWebhookPayload'
            example:
              type: "message.failed"
              webhook_id: "abe11757-2886-4b55-96f1-0e0afc95795a"
              timestamp: "2024-05-06T09:55:16.687031577Z"
              data:
                account_id: "4cdd7bdd-294e-4762-892f-83d40abf5a87"
                event: "on_failed"
                from: "sender@example.com"
                recipient: "invalid@nonexistent-domain.com"
                subject: "Welcome to our service"
                message_id_header: "<message-id-12345@localhost>"
                id: "407926766d2711f09b30960002cafe7c"
      responses:
        '200':
          description: Webhook received successfully
        '201':
          description: Webhook received successfully
        '202':
          description: Webhook received successfully
        '204':
          description: Webhook received successfully

  message.bounced:
    post:
      summary: Message Bounced Event
      description: |
        &nbsp;

        Triggered when a message reaches a bounced outcome. This includes an immediate permanent SMTP rejection or an out-of-band delivery status notification received after the message was accepted.

        A direct SMTP rejection normally includes `data.delivery_attempt`. An out-of-band bounce does not describe one SMTP attempt, so the object is omitted in that case.

        **When it's sent**: When a recipient server permanently rejects the message or AhaSend receives a later bounce notification.
      tags:
        - Message Events
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageBouncedWebhookPayload'
            example:
              type: "message.bounced"
              webhook_id: "abe11757-2886-4b55-96f1-0e0afc95795a"
              timestamp: "2024-05-06T10:05:16.687031577Z"
              data:
                account_id: "4cdd7bdd-294e-4762-892f-83d40abf5a87"
                event: "on_bounced"
                from: "sender@example.com"
                recipient: "recipient@example.com"
                subject: "Welcome to our service"
                message_id_header: "<message-id-12345@localhost>"
                id: "407926766d2711f09b30960002cafe7c"
                delivery_attempt:
                  classification: "InvalidRecipient"
                  smtp_code: 550
                  enhanced_status_code: "5.1.1"
                  response: "The email account that you tried to reach does not exist"
                  command: "RCPT TO"
      responses:
        '200':
          description: Webhook received successfully
        '201':
          description: Webhook received successfully
        '202':
          description: Webhook received successfully
        '204':
          description: Webhook received successfully

  message.suppressed:
    post:
      summary: Message Suppressed Event
      description: |
        &nbsp;

        Triggered when no delivery attempt was made because the recipient is suppressed.

        The email was not sent because the recipient email address is on your suppression list. This prevents sending to addresses that have previously bounced or been marked as problematic.

        **When it's sent**: When an email is submitted for a suppressed recipient.
      tags:
        - Message Events
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageSuppressedWebhookPayload'
            example:
              type: "message.suppressed"
              webhook_id: "abe11757-2886-4b55-96f1-0e0afc95795a"
              timestamp: "2024-05-06T10:10:16.687031577Z"
              data:
                account_id: "4cdd7bdd-294e-4762-892f-83d40abf5a87"
                event: "on_suppressed"
                from: "sender@example.com"
                recipient: "bounced@example.com"
                subject: "Welcome to our service"
                message_id_header: "<message-id-12345@localhost>"
                id: "407926766d2711f09b30960002cafe7c"
      responses:
        '200':
          description: Webhook received successfully
        '201':
          description: Webhook received successfully
        '202':
          description: Webhook received successfully
        '204':
          description: Webhook received successfully

  message.opened:
    post:
      summary: Message Opened Event
      description: |
        &nbsp;

        Triggered when the recipient opens your email.

        **Requirements**:
        - Open tracking must be enabled for your account or this specific email
        - The email must contain HTML content
        - The recipient's email client must load images

        **Additional Data**: This webhook includes user agent and IP address information for analytics.

        **Bot Detection**: The `is_bot` field indicates whether the open was detected as automated (email security scanners, etc.).

        **When it's sent**: When the tracking pixel in the email is loaded.
      tags:
        - Message Events
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageOpenedWebhookPayload'
            example:
              type: "message.opened"
              webhook_id: "abe11757-2886-4b55-96f1-0e0afc95795a"
              timestamp: "2024-05-06T10:15:16.687031577Z"
              data:
                account_id: "4cdd7bdd-294e-4762-892f-83d40abf5a87"
                event: "on_opened"
                from: "sender@example.com"
                recipient: "recipient@example.com"
                subject: "Welcome to our service"
                message_id_header: "<message-id-12345@localhost>"
                id: "407926766d2711f09b30960002cafe7c"
                user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
                ip: "192.168.1.100"
                is_bot: false
      responses:
        '200':
          description: Webhook received successfully
        '201':
          description: Webhook received successfully
        '202':
          description: Webhook received successfully
        '204':
          description: Webhook received successfully

  message.clicked:
    post:
      summary: Message Clicked Event
      description: |
        &nbsp;

        Triggered when the recipient clicks a tracked link in your email.

        **Requirements**:
        - Click tracking must be enabled for your account or this specific email
        - The clicked link must be processed by AhaSend's click tracking system

        **Additional Data**: This webhook includes user agent and IP address information for analytics.

        **When it's sent**: When a tracked link in the email is clicked.
      tags:
        - Message Events
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageClickedWebhookPayload'
            example:
              type: "message.clicked"
              webhook_id: "abe11757-2886-4b55-96f1-0e0afc95795a"
              timestamp: "2024-05-06T10:20:16.687031577Z"
              data:
                account_id: "4cdd7bdd-294e-4762-892f-83d40abf5a87"
                event: "on_clicked"
                from: "sender@example.com"
                recipient: "recipient@example.com"
                subject: "Welcome to our service"
                message_id_header: "<message-id-12345@localhost>"
                id: "407926766d2711f09b30960002cafe7c"
                url: "https://example.com/landing-page"
                user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
                ip: "192.168.1.100"
                is_bot: false
      responses:
        '200':
          description: Webhook received successfully
        '201':
          description: Webhook received successfully
        '202':
          description: Webhook received successfully
        '204':
          description: Webhook received successfully

  # Suppression Event Webhooks
  suppression.created:
    post:
      summary: Suppression Created Event
      description: |
        &nbsp;

        Triggered when a suppression is created for an email address.

        **Why suppressions are created**:
        - Multiple hard bounces from the same email address
        - Repeated soft bounces over time
        - Manual suppression through the API or dashboard

        **Auto-expiration**: Suppressions automatically expire after 30 days to allow for temporary issues to be resolved.

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

        **When it's sent**: When a new suppression is added to your account.
      tags:
        - Suppression Events
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SuppressionWebhookPayload'
            example:
              type: "suppression.created"
              webhook_id: "abe11757-2886-4b55-96f1-0e0afc95795a"
              timestamp: "2024-05-06T12:57:06.451529527Z"
              data:
                account_id: "4cdd7bdd-294e-4762-892f-83d40abf5a87"
                recipient: "bounced@example.com"
                created_at: "2024-05-06T12:57:06.451529617Z"
                expires_at: "2024-06-05T12:57:06.451529617Z"
                reason: "Too many hard bounces"
                sending_domain: "your-domain.com"
      responses:
        '200':
          description: Webhook received successfully
        '201':
          description: Webhook received successfully
        '202':
          description: Webhook received successfully
        '204':
          description: Webhook received successfully

  # Domain Event Webhooks
  domain.dns_error:
    post:
      summary: Domain DNS Error Event
      description: |
        &nbsp;

        AhaSend automatically checks your domain's DNS configuration every 15 to 30 minutes and sends this webhook if any severer issues are detected that will prevent the system from sending emails on your domain's behalf.

        Triggered when DNS configuration issues are detected for any domain in your account.

        **Common DNS issues**:
        - Missing or invalid SPF record
        - Missing or invalid DKIM record
        - Missing or invalid DMARC record
        - DNS propagation delays

        **Impact**: After this event, email sending will be rejected until DNS configuration is fixed.

        **Resolution**: Check your domain's DNS settings in the AhaSend dashboard and update your DNS records as needed.

        **When it's sent**: When AhaSend's automated DNS checker detects configuration problems.
      tags:
        - Domain Events
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DomainWebhookPayload'
            example:
              type: "domain.dns_error"
              webhook_id: "abe11757-2886-4b55-96f1-0e0afc95795a"
              timestamp: "2024-05-06T12:59:46.404433272Z"
              data:
                domain: "example.com"
                account_id: "4cdd7bdd-294e-4762-892f-83d40abf5a87"
                spf_valid: false
                dkim_valid: false
                dmarc_valid: false
                dns_last_checked_at: "2024-05-06T12:59:46.404433312Z"
      responses:
        '200':
          description: Webhook received successfully
        '201':
          description: Webhook received successfully
        '202':
          description: Webhook received successfully
        '204':
          description: Webhook received successfully

  # Route Event Webhooks (Inbound Email Processing)
  message.routing:
    post:
      summary: Inbound Message Route Event
      description: |
        &nbsp;

        Triggered when an inbound email is received and processed through a configured route.

        **Purpose**: This webhook delivers complete inbound email data to your application for processing inbound emails (like support tickets, replies, etc.).

        **Difference from message events**:
        - **Route events**: Process *inbound* emails sent TO your domains
        - **Message events**: Track *outbound* emails sent FROM your domains

        **Route configuration**:
        - Configure routes in your AhaSend dashboard
        - Choose whether to include attachments and headers
        - Set recipient filters and processing options

        When attachments are enabled, `attachments` includes conventional attachments,
        inline MIME parts (such as images referenced by `cid:` URLs in `html_body`), and
        filename-bearing MIME parts that omit the `Content-Disposition` header.

        **Use cases**:
        - Support ticket systems
        - Email-to-ticket conversion
        - Automated email processing
        - Reply handling

        **When it's sent**: When an inbound email matches a configured route.
      tags:
        - Route Events
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RouteWebhookPayload'
            example:
              type: "message.routing"
              route_id: "abe11757-2886-4b55-96f1-0e0afc95795a"
              timestamp: "2024-05-06T13:15:46.404433272Z"
              data:
                id: "route-msg-12345"
                from: "customer@gmail.com"
                reply_to: "customer@gmail.com"
                to: "support@yourdomain.com"
                subject: "Help with my account"
                message_id: "<unique-message-id@gmail.com>"
                size: 2048
                spam_score: 0.1
                bounce: false
                cc: ""
                date: "Mon, 06 May 2024 13:15:46 +0000"
                in_reply_to: ""
                references: ""
                auto_submitted: ""
                html_body: "<p>I need help with my account settings.</p>"
                plain_body: "I need help with my account settings."
                reply_from_plain_body: "I need help with my account settings."
                attachments:
                  - filename: "logo.png"
                    content_type: "image/png"
                    content_id: "logo-123"
                    disposition: "inline"
                    data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB..."
                headers:
                  "X-Mailer": "Gmail"
                  "X-Priority": "3"
      responses:
        '200':
          description: Webhook received successfully
        '201':
          description: Webhook received successfully
        '202':
          description: Webhook received successfully
        '204':
          description: Webhook received successfully

components:
  schemas:
    # Webhook payload schemas
    MessageWebhookPayload:
      type: object
      required:
        - type
        - webhook_id
        - timestamp
        - data
      properties:
        type:
          type: string
          enum:
            - message.reception
            - message.delivered
            - message.transient_error
            - message.failed
            - message.bounced
            - message.suppressed
            - message.opened
            - message.clicked
          description: The event type
        webhook_id:
          type: string
          description: The webhook ID (the same ID as returned by the [Create Webhook API](https://ahasend.com/docs/api-reference/webhooks/create-webhook))
          format: uuid
        timestamp:
          type: string
          format: date-time
          description: When the event occurred (ISO 8601 format)
        data:
          $ref: '#/components/schemas/MessageWebhookData'

    MessageReceptionWebhookPayload:
      allOf:
        - $ref: '#/components/schemas/MessageWebhookPayload'
        - type: object
          properties:
            type:
              enum:
                - message.reception
    MessageDeliveredWebhookPayload:
      allOf:
        - $ref: '#/components/schemas/MessageWebhookPayload'
        - type: object
          properties:
            type:
              enum:
                - message.delivered
    MessageTransientErrorWebhookPayload:
      allOf:
        - $ref: '#/components/schemas/MessageWebhookPayload'
        - type: object
          properties:
            type:
              enum:
                - message.transient_error
    MessageFailedWebhookPayload:
      allOf:
        - $ref: '#/components/schemas/MessageWebhookPayload'
        - type: object
          properties:
            type:
              enum:
                - message.failed
    MessageBouncedWebhookPayload:
      allOf:
        - $ref: '#/components/schemas/MessageWebhookPayload'
        - type: object
          properties:
            type:
              enum:
                - message.bounced
    MessageSuppressedWebhookPayload:
      allOf:
        - $ref: '#/components/schemas/MessageWebhookPayload'
        - type: object
          properties:
            type:
              enum:
                - message.suppressed
    MessageOpenedWebhookPayload:
      allOf:
        - $ref: '#/components/schemas/MessageWebhookPayload'
        - type: object
          properties:
            type:
              enum:
                - message.opened
    MessageClickedWebhookPayload:
      type: object
      required:
        - type
        - webhook_id
        - timestamp
        - data
      properties:
        type:
          type: string
          enum:
            - message.clicked
          description: The event type
        webhook_id:
          type: string
          description: The webhook ID (the same ID as returned by the [Create Webhook API](https://ahasend.com/docs/api-reference/webhooks/create-webhook))
          format: uuid
        timestamp:
          type: string
          format: date-time
          description: When the event occurred (ISO 8601 format)
        data:
          $ref: '#/components/schemas/MessageClickedWebhookData'

    MessageWebhookData:
      type: object
      required:
        - account_id
        - event
        - from
        - recipient
        - subject
        - message_id_header
        - id
      properties:
        account_id:
          type: string
          format: uuid
          description: Account ID that sent the message
        event:
          type: string
          enum:
            - on_reception
            - on_delivered
            - on_transient_error
            - on_failed
            - on_bounced
            - on_suppressed
            - on_opened
            - on_clicked
          description: The specific event that occurred
        from:
          type: string
          format: email
          description: Sender email address
        recipient:
          type: string
          format: email
          description: Recipient email address
        subject:
          type: string
          description: Email subject line
        message_id_header:
          type: string
          description: Message ID header (same as returned by the [Create Message API](https://ahasend.com/docs/api-reference/messages/create-message))
        id:
          type: string
          description: Internal AhaSend message ID
        user_agent:
          type: string
          description: User agent (only for opened/clicked events)
        ip:
          type: string
          description: IP address (only for opened/clicked events)
        is_bot:
          type: boolean
          description: |
            Whether the action was performed by a bot.

            Bot detection runs on every open and click and always yields a definite
            true or false. Current deliveries include the field on every message
            event; it is meaningful for `message.opened` and always `false`
            elsewhere. Older deliveries may omit it — absence carries no signal, so
            do not infer `false` from a missing field and never reject a delivery
            because of it.
        delivery_attempt:
          description: |
            Diagnostics for the delivery attempt this event reports on: the
            SMTP status code, the response text, and — on failures — the
            bucket the bounce classifier assigned.

            Optional, and absent more often than present. Today it is
            `message.delivered`, `message.bounced`, and
            `message.transient_error` that carry it, and only when an SMTP
            attempt was actually recorded. These are the cases where no attempt
            is recorded, and so no object is sent:

            - **Retry exhaustion.** A message that runs out of retries arrives
              as `message.failed`, which reports the exhaustion rather than a
              single attempt.
            - **Out-of-band bounces**, where the DSN arrives separately after
              the destination already accepted the message.
            - **Non-SMTP routing**, where the message was handled outside SMTP.
              Sandbox sends are the exception: they synthesize a representative
              attempt rather than omitting one.
            - **Empty responses**, carrying neither a status code nor any text.

            These cases are not closed, and an explicit `null` means the same
            as a missing field. Always read the object with optional chaining
            and never treat its absence as an error. Equally, do not treat its
            presence on another event sharing this data shape as impossible.
            Events with their own data shape — `message.clicked`,
            `message.routing`, `suppression.created`, `domain.dns_error` —
            never carry one.

            On test webhooks and sandbox deliveries the values are
            representative rather than observed. A sandbox simulation can
            replace the sample classification with the classification of the
            selected outcome, so it may not agree with the representative SMTP
            code and response. Do not calibrate a `classification` switch
            against sandbox traffic.
          $ref: '#/components/schemas/DeliveryAttempt'

    MessageClickedWebhookData:
      type: object
      required:
        - account_id
        - event
        - from
        - recipient
        - subject
        - message_id_header
        - url
        - user_agent
        - ip
        - id
      properties:
        account_id:
          type: string
          format: uuid
          description: Account ID that sent the message
        event:
          type: string
          description: The specific event that occurred
        from:
          type: string
          format: email
        

# --- truncated at 32 KB (47 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/ahasend/refs/heads/main/asyncapi/ahasend-webhooks-openapi.yaml

Work with this as data

Every AsyncAPI spec here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for asyncapi

4 MCP tools reach this
  • find_asyncapisBrowse and filter every AsyncAPI spec in the catalog.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This AsyncAPI spec
curl "https://apis.io/api/v1/asyncapis/ahasend-webhooks-openapi"
All asyncapi
curl "https://apis.io/api/v1/asyncapis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.