Webhooks

Event types

Every webhook event Sooner sends, what triggers it, and the fields in its data object.

Subscribe a webhook to the events you need with the events field, or use "*" for all of them. Every event uses the same envelope with type, created_at and data. See Webhooks overview.

Email events

Email events share these data fields:

FieldTypeDescription
email_idstringThe ID of the email.
fromstringThe sender.
tostring[]The recipients.
subjectstringThe subject.
created_atstringWhen the email was created, as ISO 8601.
tagsobjectThe email's tags as { "name": "value" }. Left out when the email has none.
broadcast_idstringThe broadcast the email belongs to. Only on broadcast emails.
template_idstringThe template the email was sent with. Only when one was used.
EventWhen it firesExtra data fields
email.sentSooner handed the email to the mail network.None.
email.deliveredThe receiving server accepted the email.None.
email.delivery_delayedThe receiving server has not accepted it yet. Delivery is still being attempted.delay.type
email.bouncedThe receiving server rejected the email.bounce.type, bounce.subType, bounce.message
email.complainedThe recipient marked the email as spam.complaint.type
email.openedThe recipient opened the email. Needs open tracking.open.ipAddress, open.userAgent
email.clickedThe recipient clicked a link. Needs click tracking.click.link, click.ipAddress, click.userAgent
email.failedSooner could not send the email.failed.reason
email.scheduledA scheduled email was accepted. It fires once, when you create it.None.
email.canceledA scheduled email was canceled.None.
email.suppressedThe email was accepted but not sent, because every recipient is suppressed or opted out.suppressed.type, suppressed.message

Suppressed emails

JSON
{
  "type": "email.suppressed",
  "created_at": "2026-11-14T10:30:58.000Z",
  "data": {
    "email_id": "01jcz8w3f2x9k4m7n6p5q2r1s0",
    "from": "Acme <orders@mail.yourcompany.com>",
    "to": ["nobody@example.com"],
    "subject": "Your order is confirmed",
    "created_at": "2026-11-14T10:30:58.000Z",
    "tags": { "category": "order_confirmation" },
    "suppressed": {
      "type": "OnAccountSuppressionList",
      "message": "Sooner has suppressed sending to this address because it is on the account-level suppression list."
    }
  }
}
suppressed.typeMeaning
OnAccountSuppressionListEvery recipient is on your suppression list.
UnsubscribedFromTopicThe recipient opted out of the email's topic.

The event fires when the API accepts the request. If only some recipients are suppressed, the email is sent to the rest and there is no email.suppressed event. If every recipient becomes suppressed between the request and the send, you get email.failed instead.

email.canceled and the UnsubscribedFromTopic type are Sooner additions. Resend does not send them.

Bounce details

JSON
{
  "type": "email.bounced",
  "created_at": "2026-11-14T10:31:09.000Z",
  "data": {
    "email_id": "01jcz8w3f2x9k4m7n6p5q2r1s0",
    "from": "Acme <orders@mail.yourcompany.com>",
    "to": ["nobody@example.com"],
    "subject": "Your order is confirmed",
    "created_at": "2026-11-14T10:30:58.000Z",
    "bounce": {
      "type": "Permanent",
      "subType": "General",
      "message": "smtp; 550 5.1.1 user unknown"
    }
  }
}

A Permanent bounce is a hard bounce. Sooner adds the address to your suppression list automatically. A complaint does the same. A temporary problem arrives as email.delivery_delayed first.

Opens and clicks

JSON
{
  "type": "email.clicked",
  "created_at": "2026-11-14T10:35:44.000Z",
  "data": {
    "email_id": "01jcz8w3f2x9k4m7n6p5q2r1s0",
    "from": "Acme <orders@mail.yourcompany.com>",
    "to": ["ada@example.com"],
    "subject": "Your order is confirmed",
    "created_at": "2026-11-14T10:30:58.000Z",
    "click": {
      "link": "https://yourcompany.com/orders/1042",
      "ipAddress": "203.0.113.10",
      "userAgent": "Mozilla/5.0"
    }
  }
}

Opens and clicks are recorded as events but do not change the email's status. Mail clients that preload images can produce opens that no person triggered, so treat opens as a signal and not as a count of readers.

Contact events

EventWhen it fires
contact.createdA contact was created in the dashboard or with the API.
contact.updatedA contact's name, unsubscribed flag or properties were updated through the API.
contact.deletedA contact was deleted.

The data object is the contact:

JSON
{
  "type": "contact.created",
  "created_at": "2026-11-14T10:30:00.000Z",
  "data": {
    "id": "jx7r4k9m2c5v8n1b3q6w0e2t",
    "email": "ada@example.com",
    "first_name": "Ada",
    "last_name": "Lovelace",
    "unsubscribed": false,
    "created_at": "2026-11-14T10:30:00.000Z"
  }
}

Changes a contact makes themselves on the hosted preferences page do not fire contact.updated.

Domain events

EventWhen it fires
domain.verifiedA domain's DKIM records validated and the domain can send.
domain.failedA domain's verification failed, or a verified domain stopped validating.
JSON
{
  "type": "domain.verified",
  "created_at": "2026-11-14T10:42:00.000Z",
  "data": {
    "id": "jh7b2k4d9x1m5q8r3t6v0w2y",
    "name": "mail.yourcompany.com",
    "status": "verified"
  }
}

See Add a domain for what moves a domain between statuses.

Email statuses

The webhook events map onto the email status like this. The status only moves forward, so a late delivery_delayed never undoes delivered:

EventResulting status
email.sentsent
email.delivery_delayeddelivery_delayed
email.delivereddelivered
email.bouncedbounced
email.complainedcomplained
email.failedfailed
email.opened, email.clickedNo change

See Send an email for the full list of statuses.