Webhooks

Event reference

Every event a webhook can receive, with its payload fields and an example.

Every event, as your endpoint receives it. The examples are redacted: ids, numbers and addresses are placeholders. Fields can be added over time, so ignore any you do not recognise.

SMS events

These are the SMS, opt-out and phone number events. Registering an endpoint, the envelope every event arrives in, retries and signatures are covered under Webhooks.

message.queued

An outbound message was accepted and charged, and is about to be handed to the carrier. A scheduled send fires it when you schedule it, with status SCHEDULED and scheduled_at, and goes to the carrier when it is due.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "message.queued",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "message_id": "clxxxmessagexxxxxxxxxxxxx",
    "from": "+1416XXXXXXX",
    "to": "+1613XXXXXXX",
    "status": "QUEUED",
    "message_status": "QUEUED"
  }
}
data fieldMeaning
message_idThe HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages.
fromYour HonkIO number the message was sent from, E.164.
toThe recipient's phone number, E.164.
statusThe message status at this point, upper case (the same value as message_status).
message_statusHonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED.
scheduled_atOn a scheduled send only: when it is due to go out, ISO 8601 in UTC. Its status is then SCHEDULED.

message.sending

Reserved: rarely if ever sent. It fires only when a final carrier receipt reports the sending status, which is not expected in practice.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "message.sending",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "message_id": "clxxxmessagexxxxxxxxxxxxx",
    "status": "sending",
    "message_status": "SENDING",
    "to": "+1613XXXXXXX",
    "from": "+1416XXXXXXX",
    "errors": []
  }
}
data fieldMeaning
message_idThe HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages.
statusThe carrier's own status from its receipt, lower case (for example delivered, sending_failed, delivery_failed or expired).
message_statusHonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED.
toThe recipient's phone number, E.164.
fromYour HonkIO number the message was sent from, E.164.
errorsWhat the carrier reported, as a list; empty when it reported nothing.
errors[].codeThe carrier's error code, as given.
errors[].titleA short description of the error.
errors[].detailMore detail, when the carrier gave any.

message.sent

The carrier accepted the message for delivery. Fired when the send call returns, not from a receipt.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "message.sent",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "message_id": "clxxxmessagexxxxxxxxxxxxx",
    "from": "+1416XXXXXXX",
    "to": "+1613XXXXXXX",
    "status": "SENDING",
    "message_status": "SENDING",
    "carrier_message_id": "40319xxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "segment_count": 1,
    "cost_cents": 3
  }
}
data fieldMeaning
message_idThe HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages.
fromYour HonkIO number the message was sent from, E.164.
toThe recipient's phone number, E.164.
statusThe message status at this point, upper case (the same value as message_status).
message_statusHonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED.
carrier_message_idThe id the carrier assigned to the message, for support requests.
segment_countHow many SMS segments the message was split into, which is what it was billed per.
cost_centsWhat the message cost you, in Canadian cents.

message.delivered

The carrier's receipt says the message reached the handset.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "message.delivered",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "message_id": "clxxxmessagexxxxxxxxxxxxx",
    "status": "delivered",
    "message_status": "DELIVERED",
    "to": "+1613XXXXXXX",
    "from": "+1416XXXXXXX",
    "errors": []
  }
}
data fieldMeaning
message_idThe HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages.
statusThe carrier's own status from its receipt, lower case (for example delivered, sending_failed, delivery_failed or expired).
message_statusHonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED.
toThe recipient's phone number, E.164.
fromYour HonkIO number the message was sent from, E.164.
errorsWhat the carrier reported, as a list; empty when it reported nothing.
errors[].codeThe carrier's error code, as given.
errors[].titleA short description of the error.
errors[].detailMore detail, when the carrier gave any.

message.failed

The message did not go out. Three shapes depending on where it failed: a failure at hand off to the carrier (CARRIER_UNAVAILABLE, CARRIER_THROTTLED, CARRIER_TIMEOUT, CARRIER_ERROR, INVALID_PHONE_NUMBER, NOT_A_MOBILE_NUMBER) carries error_code and error_message but no errors array; a carrier receipt carries the carrier's raw status, an errors array and an error_code, but no error_message (its error_code is CARRIER_THROTTLED when the carrier took the message and then dropped it from a full queue: that message is refunded, and resending it needs a new Idempotency-Key; it is CARRIER_LOST, with an empty errors array, when the carrier took the message and then lost it, found three days later with no receipt and no record at the carrier: refunded the same way); a send interrupted by a server restart (error_code STALE_QUEUED) carries neither to nor from. A scheduled message refused when it is due (a recipient who opted out since, for example) fails with that check's error_code and error_message, and is refunded. A scheduled message that cannot go out by its deadline (its expires_at, by default 24 hours after scheduled_at), because a pause, a sending limit or a carrier that kept throttling it held it that long, fails with error_code SCHEDULED_SEND_EXPIRED and an error_message, and is refunded. A failure for INSUFFICIENT_BALANCE fires no webhook.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "message.failed",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "message_id": "clxxxmessagexxxxxxxxxxxxx",
    "from": "+1416XXXXXXX",
    "to": "+1613XXXXXXX",
    "status": "FAILED",
    "message_status": "FAILED",
    "error_code": "INVALID_PHONE_NUMBER",
    "error_message": "The destination is not a valid phone number."
  }
}
data fieldMeaning
message_idThe HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages.
statusThe carrier's own status from its receipt, lower case (for example delivered, sending_failed, delivery_failed or expired).
message_statusHonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED.
toThe recipient's phone number, E.164.
fromYour HonkIO number the message was sent from, E.164.
error_codeHonkIO's error code for the failure, the same one the message shows in the API.
error_messageA readable explanation of error_code.
errorsWhat the carrier reported, as a list; empty when it reported nothing.
errors[].codeThe carrier's error code, as given.
errors[].titleA short description of the error.
errors[].detailMore detail, when the carrier gave any.

message.undelivered

The carrier took the message but could not deliver it: blocked, expired, or the handset was unreachable.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "message.undelivered",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "message_id": "clxxxmessagexxxxxxxxxxxxx",
    "status": "delivery_failed",
    "message_status": "UNDELIVERED",
    "to": "+1613XXXXXXX",
    "from": "+1416XXXXXXX",
    "errors": [
      {
        "code": "40002",
        "title": "Blocked as spam",
        "detail": "The destination carrier blocked the message."
      }
    ]
  }
}
data fieldMeaning
message_idThe HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages.
statusThe carrier's own status from its receipt, lower case (for example delivered, sending_failed, delivery_failed or expired).
message_statusHonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED.
toThe recipient's phone number, E.164.
fromYour HonkIO number the message was sent from, E.164.
errorsWhat the carrier reported, as a list; empty when it reported nothing.
errors[].codeThe carrier's error code, as given.
errors[].titleA short description of the error.
errors[].detailMore detail, when the carrier gave any.

message.received

Someone texted one of your HonkIO numbers. STOP, START and HELP replies arrive here too, with keyword_action saying what was done about them.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "message.received",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "message_id": "clxxxmessagexxxxxxxxxxxxx",
    "from": "+1613XXXXXXX",
    "to": "+1416XXXXXXX",
    "body": "Yes, see you at 3",
    "keyword_action": "ignored",
    "segment_count": 1,
    "cost_cents": 1,
    "message_status": "RECEIVED"
  }
}
data fieldMeaning
message_idThe HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages.
fromThe phone number that texted you, E.164.
toYour HonkIO number that received the text, E.164.
bodyThe text of the message.
keyword_actionWhat HonkIO did about a compliance keyword, judged by the first word of the text: opted_out (STOP and the like, and an opt_out.recorded event follows), reinstated (START or UNSTOP after an opt out, and an opt_out.reinstated event follows), help (HELP, INFO or AIDE; the automatic reply was sent), or ignored.
segment_countHow many SMS segments the message was split into, which is what it was billed per.
cost_centsWhat receiving the message cost you, in Canadian cents.
message_statusHonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED.

message.cancelled

A scheduled message was cancelled before it went out, and the charge was refunded.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "message.cancelled",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "message_id": "clxxxmessagexxxxxxxxxxxxx",
    "from": "+1416XXXXXXX",
    "to": "+1613XXXXXXX",
    "status": "CANCELLED",
    "message_status": "CANCELLED",
    "details": {
      "cancelled_at": "2026-10-01T16:58:00.000Z"
    }
  }
}
data fieldMeaning
message_idThe HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages.
fromYour HonkIO number the message was sent from, E.164.
toThe recipient's phone number, E.164.
statusThe message status at this point, upper case (the same value as message_status).
message_statusHonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED.
details.cancelled_atWhen the message was cancelled, ISO 8601 in UTC.

message.rescheduled

A scheduled message was moved to a new time.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "message.rescheduled",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "message_id": "clxxxmessagexxxxxxxxxxxxx",
    "from": "+1416XXXXXXX",
    "to": "+1613XXXXXXX",
    "status": "SCHEDULED",
    "message_status": "SCHEDULED",
    "details": {
      "previous_scheduled_at": "2026-10-02T13:00:00.000Z",
      "scheduled_at": "2026-10-03T13:00:00.000Z"
    }
  }
}
data fieldMeaning
message_idThe HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages.
fromYour HonkIO number the message was sent from, E.164.
toThe recipient's phone number, E.164.
statusThe message status at this point, upper case (the same value as message_status).
message_statusHonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED.
details.previous_scheduled_atThe time it was scheduled for before, ISO 8601 in UTC.
details.scheduled_atThe new time, ISO 8601 in UTC.

opt_out.recorded

A subscriber replied to one of your numbers with an opt out keyword (a reply whose first word is STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT or the French ARRET, with or without its accent), and messages to them from that number are now blocked. Fires only for a keyword reply: an opt out you record with POST /v1/compliance/opt-outs does not fire it.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "opt_out.recorded",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "phone_number": "+1613XXXXXXX",
    "from_number": "+1416XXXXXXX",
    "keyword": "STOP"
  }
}
data fieldMeaning
phone_numberThe subscriber who texted the keyword, E.164: the person to stop or resume messaging.
from_numberYour HonkIO number that received the keyword, E.164. An opt out applies to messages from this number.
keywordThe subscriber's reply, trimmed and upper case, e.g. STOP or STOP PLEASE. The whole reply, not only the keyword: it was recognised by its first word.

opt_out.reinstated

A subscriber who had opted out replied with START or UNSTOP, so you may message them again from that number. Fires only for a keyword reply, and only when an opt out existed: a START from someone who never opted out is an ordinary message.received.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "opt_out.reinstated",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "phone_number": "+1613XXXXXXX",
    "from_number": "+1416XXXXXXX",
    "keyword": "START"
  }
}
data fieldMeaning
phone_numberThe subscriber who texted the keyword, E.164: the person to stop or resume messaging.
from_numberYour HonkIO number that received the keyword, E.164. An opt out applies to messages from this number.
keywordThe subscriber's reply, trimmed and upper case, e.g. START. The whole reply, not only the keyword: it was recognised by its first word.

phone_number.suspended

Monthly rent could not be charged, so the number was suspended. Top up to bring it back before the grace period ends.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "phone_number.suspended",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "phone_number": "+1416XXXXXXX",
    "reason": "insufficient_balance",
    "monthly_cost_cents": 299
  }
}
data fieldMeaning
phone_numberYour HonkIO number, E.164.
reasonAlways insufficient_balance: the month's rent could not be charged.
monthly_cost_centsThe number's monthly rent, in Canadian cents.

phone_number.released

The number has left your account and cannot be recovered. Stop routing to it.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "phone_number.released",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "phone_number": "+1416XXXXXXX",
    "reason": "customer_released",
    "rent_owed_cents": 0
  }
}
data fieldMeaning
phone_numberYour HonkIO number, E.164.
reasonsuspended_grace_expired when a suspension ran past its grace period, customer_released when you released it yourself, admin_released when HonkIO staff released it.
rent_owed_centsRent now owed for this number, in Canadian cents. It is the number's monthly rent when a suspended number was released, and 0 when an active one was.

toll_free_verification.changes_requested

HonkIO reviewed your toll-free verification application and needs changes before sending it to the carrier. Edit it as reviewer_note says and submit it again; nothing more is charged.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "toll_free_verification.changes_requested",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "verification_id": "tfv_xxxxxxxxxxxxxxxxxxxxxxxx",
    "status": "CHANGES_REQUESTED",
    "phone_numbers": [
      {
        "id": "clxxxnumberxxxxxxxxxxxxxx",
        "phone_number": "+1833XXXXXXX"
      }
    ],
    "reviewer_note": "The opt-in screenshot does not show the SMS checkbox. Add one that does."
  }
}
data fieldMeaning
verification_idThe toll-free verification application id (tfv_...), as returned by POST /v1/toll-free-verifications.
statusThe application’s status after the decision: CHANGES_REQUESTED, APPROVED or REJECTED, matching the event.
phone_numbersThe toll-free numbers the application covers.
phone_numbers[].idThe phone number id, as GET /v1/phone-numbers lists it.
phone_numbers[].phone_numberThe toll-free number, E.164.
reviewer_noteWhat HonkIO asks you to change, as the dashboard shows it.

toll_free_verification.approved

The carrier approved your toll-free verification application: every number on it can now send SMS.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "toll_free_verification.approved",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "verification_id": "tfv_xxxxxxxxxxxxxxxxxxxxxxxx",
    "status": "APPROVED",
    "phone_numbers": [
      {
        "id": "clxxxnumberxxxxxxxxxxxxxx",
        "phone_number": "+1833XXXXXXX"
      }
    ]
  }
}
data fieldMeaning
verification_idThe toll-free verification application id (tfv_...), as returned by POST /v1/toll-free-verifications.
statusThe application’s status after the decision: CHANGES_REQUESTED, APPROVED or REJECTED, matching the event.
phone_numbersThe toll-free numbers the application covers.
phone_numbers[].idThe phone number id, as GET /v1/phone-numbers lists it.
phone_numbers[].phone_numberThe toll-free number, E.164.

toll_free_verification.rejected

The carrier rejected your toll-free verification application. Fix what reason names and submit it again, at no charge.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "toll_free_verification.rejected",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "verification_id": "tfv_xxxxxxxxxxxxxxxxxxxxxxxx",
    "status": "REJECTED",
    "phone_numbers": [
      {
        "id": "clxxxnumberxxxxxxxxxxxxxx",
        "phone_number": "+1833XXXXXXX"
      }
    ],
    "reason": "Opt-in workflow does not show how consent is collected."
  }
}
data fieldMeaning
verification_idThe toll-free verification application id (tfv_...), as returned by POST /v1/toll-free-verifications.
statusThe application’s status after the decision: CHANGES_REQUESTED, APPROVED or REJECTED, matching the event.
phone_numbersThe toll-free numbers the application covers.
phone_numbers[].idThe phone number id, as GET /v1/phone-numbers lists it.
phone_numbers[].phone_numberThe toll-free number, E.164.
reasonThe carrier’s reason for the rejection, as it gave it (usually in English).

Account events

Seven account-level events carry no message: account.delivery_warning when more than 10% of your last 50 live messages failed at the carrier; account.sending_paused when an automatic pause trips (channel is sms or email; an email pause adds bounce_rate_pct or complaint_rate_pct); account.spend_warning when an hour's live spend exceeds the larger of $5 and half a typical day; account.inbound_email_capped when received email hits the account's daily cap, at most once per 24 hours; account.inbound_sms_capped when received texts hit the account's daily cap, at most once per 24 hours; account.auto_topup_succeeded when an auto top-up charge credits your balance; account.auto_topup_paused when auto top-up pauses. Each is also emailed. Their payloads are in the reference below.

account.delivery_warning

More than 10% of your last 50 live messages failed at the carrier. Sent before any automatic pause, at most once per pause window. Also emailed.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "account.delivery_warning",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "failure_rate_pct": 14,
    "failed": 7,
    "sample": 50
  }
}
data fieldMeaning
failure_rate_pctThe share of the sample that failed, in percent, rounded.
failedHow many messages in the sample failed at the carrier.
sampleHow many of your most recent live messages were looked at.

account.sending_paused

An automatic pause stopped live sending until paused_until. Test mode is not affected. Also emailed.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "account.sending_paused",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "channel": "sms",
    "paused_until": "2026-09-25T14:00:00.000Z",
    "reason": "FAILURE_RATE"
  }
}
data fieldMeaning
channelWhich sending stopped: sms or email. The other channel is not paused.
paused_untilWhen live sending resumes on its own, ISO 8601 in UTC.
reasonWhy: OPT_OUT_RATE or FAILURE_RATE for SMS; BOUNCE_RATE or COMPLAINT_RATE for email.
bounce_rate_pctEmail pauses for BOUNCE_RATE only: your recent live bounce rate, in percent.
complaint_rate_pctEmail pauses for COMPLAINT_RATE only: your recent live complaint rate, in percent.

account.spend_warning

Your live spend in the last hour exceeded the larger of $5 and half a typical day. Also emailed.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "account.spend_warning",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "spent_last_hour_cents": 1250,
    "typical_daily_cents": 400,
    "threshold_cents": 500
  }
}
data fieldMeaning
spent_last_hour_centsYour live spend in the last hour, in Canadian cents.
typical_daily_centsWhat your account usually spends in a whole day, in Canadian cents, averaged over its live sending in the past week; 0 until it has two days of live sending, or with none that week.
threshold_centsThe spend in an hour that triggers this warning for your account, in Canadian cents.

account.inbound_email_capped

Received email for this account hit its daily cap. Further mail is stored as rejected and not charged until the 24-hour window moves on.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "account.inbound_email_capped",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "limit": "messages",
    "cap": 1000,
    "window_hours": 24,
    "received_in_window": 1000,
    "managed_address_enabled": true
  }
}
data fieldMeaning
limitWhich cap was hit: messages (the daily message count) or megabytes (the daily cap on billable MB, the started MB after the first MB of each message; cap and received_in_window are then in MB).
capThe daily cap that was hit: your account's own override, or the platform default.
window_hoursThe rolling window the cap counts over, in hours. Always 24.
received_in_windowHow many messages your account received in the window, at the moment the cap was hit. For account.inbound_sms_capped, message segments (a long text counts as several).
managed_address_enabledWhether your managed inbound address is still accepting mail. Switch it off from the Inbox if this traffic should not reach it.

account.inbound_sms_capped

Received texts for this account hit its daily cap, counted in message segments (a long text counts as several). Further texts are stored without their content and not charged until the 24-hour window moves on. STOP, START and HELP keep working.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "account.inbound_sms_capped",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "cap": 1000,
    "window_hours": 24,
    "received_in_window": 1000
  }
}
data fieldMeaning
capThe daily cap that was hit: your account's own override, or the platform default.
window_hoursThe rolling window the cap counts over, in hours. Always 24.
received_in_windowHow many messages your account received in the window, at the moment the cap was hit. For account.inbound_sms_capped, message segments (a long text counts as several).

account.auto_topup_succeeded

An auto top-up charge succeeded and your balance was credited. The receipt is also emailed.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "account.auto_topup_succeeded",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "attempt_id": "ata_a1b2c3d4e5f6a1b2c3d4e5f6",
    "trigger": "threshold",
    "amount_cents": 2500,
    "tax_cents": 325,
    "total_cents": 2825,
    "balance_cents": 3100
  }
}
data fieldMeaning
attempt_idThe id of the auto top-up attempt, as listed by GET /v1/auto-topup. On account.auto_topup_paused it is the attempt that caused the pause, or null when no charge was involved.
triggerWhat started the charge: threshold when your balance fell below your threshold, rent when it would not cover rent due within 24 hours.
amount_centsThe amount credited to your balance, in Canadian cents, before tax.
tax_centsThe sales tax added to the charge, in Canadian cents.
total_centsThe total charged to your card, in Canadian cents.
balance_centsYour balance after the credit, in Canadian cents.

account.auto_topup_paused

Auto top-up was paused and will not charge again until you turn it back on with Top up to resume in the Auto top-up section of the Billing page. A pause for account review is lifted by HonkIO support instead. Also emailed.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "account.auto_topup_paused",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "reason": "DECLINED",
    "attempt_id": "ata_a1b2c3d4e5f6a1b2c3d4e5f6"
  }
}
data fieldMeaning
reasonWhy it paused, upper case: DECLINED, AUTHENTICATION_REQUIRED, EXPIRED_CARD, CARD_REMOVED, BILLING_ADDRESS, ACCOUNT_REVIEW, ADMIN, REFUND or CHARGE_LIMIT.
attempt_idThe id of the auto top-up attempt, as listed by GET /v1/auto-topup. On account.auto_topup_paused it is the attempt that caused the pause, or null when no charge was involved.

Email events

Subscribe to email events the same way as SMS events. Deliveries are signed exactly as described in Verifying signatures.

email.queued

An email was accepted and charged, and is queued, or scheduled when you passed scheduled_at.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email.queued",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "email": {
      "id": "clxxxemailxxxxxxxxxxxxxxx",
      "from": "receipts@mail.acme.ca",
      "to": [
        "ada@example.com"
      ],
      "subject": "Your receipt",
      "tags": [
        {
          "name": "category",
          "value": "receipt"
        }
      ],
      "status": "queued",
      "is_commercial": false
    }
  }
}
data fieldMeaning
email.idThe HonkIO email id, as returned by POST /v1/emails.
email.fromThe from address the email was sent with.
email.toThe recipient addresses, as a list.
email.subjectThe subject line.
email.tagsThe tags you set on the email, as a list of name and value pairs; empty when there are none.
email.statusqueued, or scheduled when the email waits for scheduled_at. Absent on emails sent as part of a batch.
email.batch_idEmails sent through POST /v1/emails/batch only: the batch the email belongs to.
email.is_commercialWhether you marked the email as commercial (CASL), which decides the unsubscribe footer and header.

email.sent

The email provider accepted the message for delivery.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email.sent",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "email": {
      "id": "clxxxemailxxxxxxxxxxxxxxx",
      "ses_message_id": "0101019xxxxxxxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-000000",
      "from": "receipts@mail.acme.ca",
      "to": [
        "ada@example.com"
      ],
      "subject": "Your receipt",
      "tags": []
    }
  }
}
data fieldMeaning
email.idThe HonkIO email id, as returned by POST /v1/emails.
email.fromThe from address the email was sent with.
email.toThe recipient addresses, as a list.
email.ses_message_idThe email provider's id for the message, for support requests. Present on the events the provider reports.
email.subjectThe subject line.
email.tagsThe tags you set on the email, as a list of name and value pairs; empty when there are none.

email.delivered

The receiving mail server accepted the message.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email.delivered",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "email": {
      "id": "clxxxemailxxxxxxxxxxxxxxx",
      "to": [
        "ada@example.com"
      ],
      "from": "receipts@mail.acme.ca",
      "ses_message_id": "0101019xxxxxxxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-000000",
      "tags": [
        {
          "name": "category",
          "value": "receipt"
        }
      ]
    },
    "details": {
      "delivered_at": "2026-10-01T17:00:03.512Z",
      "smtp_response": "250 2.0.0 OK",
      "processing_time_ms": 1204,
      "recipients": [
        "ada@example.com"
      ],
      "ses_message_id": "0101019xxxxxxxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-000000"
    }
  }
}
data fieldMeaning
email.idThe HonkIO email id, as returned by POST /v1/emails.
email.toThe recipient addresses, as a list.
email.fromThe from address the email was sent with.
email.ses_message_idThe email provider's id for the message, for support requests. Present on the events the provider reports.
email.tagsThe tags you set on the email, as a list of name and value pairs; empty when there are none.
details.delivered_atWhen the receiving server accepted the message, ISO 8601 in UTC.
details.smtp_responseThe receiving server's SMTP answer, as given.
details.processing_time_msHow long delivery took from acceptance, in milliseconds.
details.recipientsThe recipient addresses this report covers.
details.ses_message_idThe same provider id as email.ses_message_id.

email.delivery_delayed

The receiving server is deferring the message; delivery is still being retried. Several can arrive for one email.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email.delivery_delayed",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "email": {
      "id": "clxxxemailxxxxxxxxxxxxxxx",
      "to": [
        "ada@example.com"
      ],
      "from": "receipts@mail.acme.ca",
      "ses_message_id": "0101019xxxxxxxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-000000",
      "tags": [
        {
          "name": "category",
          "value": "receipt"
        }
      ]
    },
    "details": {
      "delay_type": "MailboxFull",
      "expiration_time": "2026-10-02T17:00:00.000Z",
      "recipients": [
        {
          "email_address": "ada@example.com",
          "status": "4.2.2",
          "diagnostic_code": "smtp; 452 4.2.2 Mailbox full"
        }
      ],
      "ses_message_id": "0101019xxxxxxxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-000000"
    }
  }
}
data fieldMeaning
email.idThe HonkIO email id, as returned by POST /v1/emails.
email.toThe recipient addresses, as a list.
email.fromThe from address the email was sent with.
email.ses_message_idThe email provider's id for the message, for support requests. Present on the events the provider reports.
email.tagsThe tags you set on the email, as a list of name and value pairs; empty when there are none.
details.delay_typeThe kind of delay the provider reported, e.g. MailboxFull, SpamDetected or TransientCommunicationFailure.
details.expiration_timeWhen the provider will stop retrying and bounce the message, ISO 8601 in UTC.
details.recipientsThe recipients whose delivery is delayed, one object each.
details.recipients[].email_addressA delayed recipient's address.
details.recipients[].statusThe SMTP status code for that recipient.
details.recipients[].diagnostic_codeThe receiving server's own explanation, when it gave one.
details.ses_message_idThe same provider id as email.ses_message_id.

email.bounced

The message bounced. A permanent bounce suppresses the address; a transient one does not.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email.bounced",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "email": {
      "id": "clxxxemailxxxxxxxxxxxxxxx",
      "to": [
        "ada@example.com"
      ],
      "from": "receipts@mail.acme.ca",
      "ses_message_id": "0101019xxxxxxxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-000000",
      "tags": [
        {
          "name": "category",
          "value": "receipt"
        }
      ]
    },
    "details": {
      "bounce_type": "Permanent",
      "bounce_subtype": "General",
      "diagnostic_code": "smtp; 550 5.1.1 user unknown",
      "suppressed": true,
      "transient": false,
      "recipients": [
        "ada@example.com"
      ],
      "ses_message_id": "0101019xxxxxxxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-000000"
    }
  }
}
data fieldMeaning
email.idThe HonkIO email id, as returned by POST /v1/emails.
email.toThe recipient addresses, as a list.
email.fromThe from address the email was sent with.
email.ses_message_idThe email provider's id for the message, for support requests. Present on the events the provider reports.
email.tagsThe tags you set on the email, as a list of name and value pairs; empty when there are none.
details.bounce_typePermanent, Transient or Undetermined.
details.bounce_subtypeThe provider's finer classification, e.g. General, NoEmail or MailboxFull.
details.diagnostic_codeThe receiving server's own explanation for the first bounced recipient, when it gave one.
details.suppressedtrue when the address was added to your suppression list (every permanent bounce).
details.transienttrue for a temporary bounce, which does not suppress the address.
details.recipientsThe recipient addresses this report covers.
details.ses_message_idThe same provider id as email.ses_message_id.

email.complained

A recipient marked the message as spam. The address is suppressed and recorded as an opt out.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email.complained",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "email": {
      "id": "clxxxemailxxxxxxxxxxxxxxx",
      "to": [
        "ada@example.com"
      ],
      "from": "receipts@mail.acme.ca",
      "ses_message_id": "0101019xxxxxxxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-000000",
      "tags": [
        {
          "name": "category",
          "value": "receipt"
        }
      ]
    },
    "details": {
      "complained_at": "2026-10-01T18:12:00.000Z",
      "recipients": [
        "ada@example.com"
      ],
      "ses_message_id": "0101019xxxxxxxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-000000"
    }
  }
}
data fieldMeaning
email.idThe HonkIO email id, as returned by POST /v1/emails.
email.toThe recipient addresses, as a list.
email.fromThe from address the email was sent with.
email.ses_message_idThe email provider's id for the message, for support requests. Present on the events the provider reports.
email.tagsThe tags you set on the email, as a list of name and value pairs; empty when there are none.
details.complained_atWhen the complaint was made, ISO 8601 in UTC.
details.recipientsThe recipient addresses this report covers.
details.ses_message_idThe same provider id as email.ses_message_id.

email.rejected

The message was refused before or at sending, by HonkIO's checks or by the email provider, and the charge is refunded. A rejection from our checks carries failure_code and failure_message; one reported by the provider carries reason.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email.rejected",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "email": {
      "id": "clxxxemailxxxxxxxxxxxxxxx",
      "from": "receipts@mail.acme.ca",
      "to": [
        "ada@example.com"
      ]
    },
    "details": {
      "failure_code": "DOMAIN_NOT_VERIFIED",
      "failure_message": "The sending domain is not verified."
    }
  }
}
data fieldMeaning
email.idThe HonkIO email id, as returned by POST /v1/emails.
email.fromThe from address the email was sent with.
email.toThe recipient addresses, as a list.
details.failure_codeHonkIO's code for why the email was refused or failed.
details.failure_messageA readable explanation of failure_code.
details.reasonRejections reported by the email provider only (in place of failure_code): the provider's reason.
email.ses_message_idThe email provider's id for the message, for support requests. Present on the events the provider reports.

email.failed

The email could not be sent for a reason that is not about the recipient, such as a billing or provider error, and the charge is refunded.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email.failed",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "email": {
      "id": "clxxxemailxxxxxxxxxxxxxxx",
      "from": "receipts@mail.acme.ca",
      "to": [
        "ada@example.com"
      ]
    },
    "details": {
      "failure_code": "SES_ERROR",
      "failure_message": "The provider did not accept the message."
    }
  }
}
data fieldMeaning
email.idThe HonkIO email id, as returned by POST /v1/emails.
email.fromThe from address the email was sent with.
email.toThe recipient addresses, as a list.
details.failure_codeHonkIO's code for why the email was refused or failed.
details.failure_messageA readable explanation of failure_code.

email.opened

The first open of the email: the tracking pixel loaded. Only when open tracking is on; some mail clients load images automatically, so an open is a hint, not proof. Later opens fire no webhook.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email.opened",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "email": {
      "id": "clxxxemailxxxxxxxxxxxxxxx",
      "from": "receipts@mail.acme.ca",
      "to": [
        "ada@example.com"
      ]
    },
    "details": {
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X)"
    }
  }
}
data fieldMeaning
email.idThe HonkIO email id, as returned by POST /v1/emails.
email.fromThe from address the email was sent with.
email.toThe recipient addresses, as a list.
details.uaThe user agent that loaded the pixel or followed the link, or null.

email.clicked

The first click on a tracked link in the email, when click tracking is on. Later clicks fire no webhook.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email.clicked",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "email": {
      "id": "clxxxemailxxxxxxxxxxxxxxx",
      "from": "receipts@mail.acme.ca",
      "to": [
        "ada@example.com"
      ]
    },
    "details": {
      "url": "https://acme.ca/orders/1042",
      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5)"
    }
  }
}
data fieldMeaning
email.idThe HonkIO email id, as returned by POST /v1/emails.
email.fromThe from address the email was sent with.
email.toThe recipient addresses, as a list.
details.urlThe link that was followed, as written in your email.
details.uaThe user agent that loaded the pixel or followed the link, or null.

email.unsubscribed

A recipient of a commercial email unsubscribed through its link or header, and is now suppressed for that sending domain.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email.unsubscribed",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "email": {
      "id": "clxxxemailxxxxxxxxxxxxxxx",
      "from": "receipts@mail.acme.ca",
      "to": [
        "ada@example.com"
      ]
    },
    "details": {
      "recipient": "ada@example.com",
      "from_domain": "mail.acme.ca"
    }
  }
}
data fieldMeaning
email.idThe HonkIO email id, as returned by POST /v1/emails.
email.fromThe from address the email was sent with.
email.toThe recipient addresses, as a list.
details.recipientThe address that unsubscribed.
details.from_domainThe sending domain the address is now suppressed for.

email.cancelled

A scheduled email was cancelled before it went out, and the charge was refunded.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email.cancelled",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "email": {
      "id": "clxxxemailxxxxxxxxxxxxxxx",
      "from": "receipts@mail.acme.ca",
      "to": [
        "ada@example.com"
      ]
    },
    "details": {
      "cancelled_at": "2026-10-01T16:58:00.000Z"
    }
  }
}
data fieldMeaning
email.idThe HonkIO email id, as returned by POST /v1/emails.
email.fromThe from address the email was sent with.
email.toThe recipient addresses, as a list.
details.cancelled_atWhen the email was cancelled, ISO 8601 in UTC.

email.rescheduled

A scheduled email was moved to a new time.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email.rescheduled",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "email": {
      "id": "clxxxemailxxxxxxxxxxxxxxx",
      "from": "receipts@mail.acme.ca",
      "to": [
        "ada@example.com"
      ],
      "tags": []
    },
    "details": {
      "previous_scheduled_at": "2026-10-02T13:00:00.000Z",
      "scheduled_at": "2026-10-03T13:00:00.000Z"
    }
  }
}
data fieldMeaning
email.idThe HonkIO email id, as returned by POST /v1/emails.
email.fromThe from address the email was sent with.
email.toThe recipient addresses, as a list.
email.tagsThe tags you set on the email, as a list of name and value pairs; empty when there are none.
details.previous_scheduled_atThe time it was scheduled for before, ISO 8601 in UTC.
details.scheduled_atThe new time, ISO 8601 in UTC.

email.received

An email arrived at one of your receiving domains or your managed inbound address. Metadata only; fetch the body by id.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email.received",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "email": {
      "id": "rcv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "from": "ada@example.com",
      "from_name": "Ada Lovelace",
      "to": [
        "support@abc123defg.inbound.honkio.ca"
      ],
      "cc": [],
      "subject": "Re: Your order",
      "message_id": "<abc123@example.com>",
      "in_reply_to": "<orig456@mail.acme.ca>",
      "received_at": "2026-10-01T17:00:00.000Z",
      "size_bytes": 12345,
      "domain_id": null,
      "verdicts": {
        "spf": "PASS",
        "dkim": "PASS",
        "dmarc": "GRAY",
        "spam": "PASS"
      },
      "attachments": [
        {
          "id": "rat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
          "filename": "invoice.pdf",
          "content_type": "application/pdf",
          "size_bytes": 48213,
          "inline": false
        }
      ]
    }
  }
}
data fieldMeaning
email.idThe received email's id (rcv_…). Fetch its body with GET /v1/emails/received/:id.
email.fromThe sender's address, as the From header gave it.
email.from_nameThe sender's display name, or null.
email.toThe To header addresses.
email.ccThe Cc header addresses.
email.subjectThe subject line.
email.message_idThe RFC 5322 Message-ID, with angle brackets. To reply in the same thread, send it as the In-Reply-To header (and at the end of References) in the send API's headers map.
email.in_reply_toThe Message-ID this email replies to, or null.
email.received_atWhen the message reached us, ISO 8601 UTC.
email.size_bytesThe raw message size, headers included.
email.domain_idThe receiving domain's id, or null for your managed inbound address.
email.verdictsAuthentication and spam results from the receiving check. PASS, FAIL, GRAY or PROCESSING_FAILED.
email.verdicts.spfSPF result for the envelope sender.
email.verdicts.dkimDKIM signature result.
email.verdicts.dmarcDMARC result for the From domain.
email.verdicts.spamSpam scan result. Messages that scanned as a virus are never delivered.
email.attachmentsThe attachments, metadata only. Download each with GET /v1/emails/received/:id/attachments/:attachment_id within 40 days.
email.attachments[].idThe attachment id (rat_…).
email.attachments[].filenameThe filename as sent, or attachment-N when it had none.
email.attachments[].content_typeThe declared content type.
email.attachments[].size_bytesSize in bytes.
email.attachments[].inlinetrue for an inline part such as an embedded image.

email_domain.verified

A sending domain's DNS records checked out and it can send.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email_domain.verified",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "domain": {
      "id": "clxxxdomainxxxxxxxxxxxxxx",
      "domain": "mail.acme.ca",
      "region": "ca-central-1"
    }
  }
}
data fieldMeaning
domain.idThe HonkIO email domain id.
domain.domainThe domain name.
domain.regionThe region the domain sends from (ca-central-1).

email_domain.verification_failed

A sending domain failed verification, or lost it on a later re-check of its records.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email_domain.verification_failed",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "domain": {
      "id": "clxxxdomainxxxxxxxxxxxxxx",
      "domain": "mail.acme.ca",
      "status": "temporarily_failed"
    },
    "details": {
      "failure_reason": "DKIM records not found",
      "revalidation": true
    }
  }
}
data fieldMeaning
domain.idThe HonkIO email domain id.
domain.domainThe domain name.
domain.statusfailed when another account has since verified the same domain, temporarily_failed when a re-check of an already verified domain failed; absent when a first verification failed.
details.failure_reasonWhy verification failed, e.g. which records were not found.
details.revalidationtrue when this was a re-check of a domain that had verified before. Absent otherwise.

email_domain.receiving_verified

The inbound MX record for a domain was found. Mail sent to it now reaches you.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email_domain.receiving_verified",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "domain": {
      "id": "clxxxdomainxxxxxxxxxxxxxx",
      "domain": "reply.acme.ca"
    }
  }
}
data fieldMeaning
domain.idThe HonkIO email domain id.
domain.domainThe domain name.

email_domain.receiving_failed

The inbound MX record has been missing past the grace period. Receiving on this domain is off until the record is back and the domain is re-verified.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "email_domain.receiving_failed",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "domain": {
      "id": "clxxxdomainxxxxxxxxxxxxxx",
      "domain": "reply.acme.ca",
      "status": "failed"
    },
    "details": {
      "failure_reason": "Inbound MX missing on reply.acme.ca",
      "revalidation": true
    }
  }
}
data fieldMeaning
domain.idThe HonkIO email domain id.
domain.domainThe domain name.
domain.statusAlways failed: the inbound MX record has been missing for longer than the grace period. Receiving stays off until the record is back and the domain is checked again.
details.failure_reasonWhy verification failed, e.g. which records were not found.
details.revalidationtrue when this was a re-check of a domain that had verified before. Absent otherwise.

Push events

Nine events cover pushes (push.queued, push.sent, push.failed, push.fell_back, push.cancelled, push.rescheduled), devices (push_device.deactivated) and apps (push_app.credentials_invalid, push_app.vapid_key_replaced). They are listed with when each fires and its data fields under push webhooks.

Notify events

Two events cover notifications: notification.delivered and notification.failed. Each is listed below with its data fields; notify webhooks explains how they relate to the events of the sends behind a notification.

notification.delivered

A channel delivered the notification: channel names it, and the id of the SMS, email or push it sent is set. Fires once per notification; the send behind it also fires its own events.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "notification.delivered",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "id": "ntf_xxxxxxxxxxxxxxxxxxxxxxxx",
    "user_id": "user_1042",
    "status": "delivered",
    "channel": "email",
    "message_id": null,
    "email_id": "clxxxemailxxxxxxxxxxxxxxx",
    "push_id": null,
    "attempts": [
      {
        "channel": "sms",
        "outcome": "skipped",
        "reason": "no_phone_number"
      },
      {
        "channel": "email",
        "outcome": "delivered",
        "reason": null
      }
    ],
    "livemode": true
  }
}
data fieldMeaning
idThe notification id, as returned by POST /v1/notify and read by GET /v1/notifications/:id.
user_idThe user_id the notification was sent to: your id for the recipient.
statusThe notification's final status, lower case: delivered or failed, matching the event.
channelThe channel that delivered it, one of the channels in attempts; null when none did.
message_idThe id of the SMS that delivered it, as GET /v1/messages lists it; null unless channel is sms.
email_idThe id of the email that delivered it, as GET /v1/emails lists it; null unless channel is email.
push_idThe id of the push that delivered it; null unless channel is push.
attemptsThe channels tried, in order: up to the one that delivered it, or every channel when none did.
attempts[].channelThe channel tried.
attempts[].outcomeWhat happened on it, lower case: delivered, skipped (the person could not be reached on it, or it could not be used), refused (the channel turned the send down) or failed.
attempts[].reasonWhy, for any outcome but delivered: a lower case reason for a skip, such as no_phone_number or no_email, or the channel's own error code, such as NO_CONSENT or EMAIL_SUPPRESSED. null when delivered.
livemodetrue for a notification sent with a live key, false for a test key, as in the envelope.

notification.failed

No channel delivered the notification: each channel in its order was skipped, refused or failed, and attempts says why for each. Fires once per notification.

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "notification.failed",
  "created": "2026-09-24T12:00:00.000Z",
  "account_id": "clxxxaccountxxxxxxxxxxxxx",
  "livemode": true,
  "data": {
    "id": "ntf_xxxxxxxxxxxxxxxxxxxxxxxx",
    "user_id": "user_1042",
    "status": "failed",
    "channel": null,
    "message_id": null,
    "email_id": null,
    "push_id": null,
    "attempts": [
      {
        "channel": "sms",
        "outcome": "refused",
        "reason": "NO_CONSENT"
      },
      {
        "channel": "email",
        "outcome": "skipped",
        "reason": "no_email"
      }
    ],
    "livemode": true
  }
}
data fieldMeaning
idThe notification id, as returned by POST /v1/notify and read by GET /v1/notifications/:id.
user_idThe user_id the notification was sent to: your id for the recipient.
statusThe notification's final status, lower case: delivered or failed, matching the event.
channelThe channel that delivered it, one of the channels in attempts; null when none did.
message_idThe id of the SMS that delivered it, as GET /v1/messages lists it; null unless channel is sms.
email_idThe id of the email that delivered it, as GET /v1/emails lists it; null unless channel is email.
push_idThe id of the push that delivered it; null unless channel is push.
attemptsThe channels tried, in order: up to the one that delivered it, or every channel when none did.
attempts[].channelThe channel tried.
attempts[].outcomeWhat happened on it, lower case: delivered, skipped (the person could not be reached on it, or it could not be used), refused (the channel turned the send down) or failed.
attempts[].reasonWhy, for any outcome but delivered: a lower case reason for a skip, such as no_phone_number or no_email, or the channel's own error code, such as NO_CONSENT or EMAIL_SUPPRESSED. null when delivered.
livemodetrue for a notification sent with a live key, false for a test key, as in the envelope.