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.
{
"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 field | Meaning |
|---|---|
| message_id | The HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages. |
| from | Your HonkIO number the message was sent from, E.164. |
| to | The recipient's phone number, E.164. |
| status | The message status at this point, upper case (the same value as message_status). |
| message_status | HonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED. |
| scheduled_at | On 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.
{
"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 field | Meaning |
|---|---|
| message_id | The HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages. |
| status | The carrier's own status from its receipt, lower case (for example delivered, sending_failed, delivery_failed or expired). |
| message_status | HonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED. |
| to | The recipient's phone number, E.164. |
| from | Your HonkIO number the message was sent from, E.164. |
| errors | What the carrier reported, as a list; empty when it reported nothing. |
| errors[].code | The carrier's error code, as given. |
| errors[].title | A short description of the error. |
| errors[].detail | More 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.
{
"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 field | Meaning |
|---|---|
| message_id | The HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages. |
| from | Your HonkIO number the message was sent from, E.164. |
| to | The recipient's phone number, E.164. |
| status | The message status at this point, upper case (the same value as message_status). |
| message_status | HonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED. |
| carrier_message_id | The id the carrier assigned to the message, for support requests. |
| segment_count | How many SMS segments the message was split into, which is what it was billed per. |
| cost_cents | What the message cost you, in Canadian cents. |
message.delivered
The carrier's receipt says the message reached the handset.
{
"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 field | Meaning |
|---|---|
| message_id | The HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages. |
| status | The carrier's own status from its receipt, lower case (for example delivered, sending_failed, delivery_failed or expired). |
| message_status | HonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED. |
| to | The recipient's phone number, E.164. |
| from | Your HonkIO number the message was sent from, E.164. |
| errors | What the carrier reported, as a list; empty when it reported nothing. |
| errors[].code | The carrier's error code, as given. |
| errors[].title | A short description of the error. |
| errors[].detail | More 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.
{
"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 field | Meaning |
|---|---|
| message_id | The HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages. |
| status | The carrier's own status from its receipt, lower case (for example delivered, sending_failed, delivery_failed or expired). |
| message_status | HonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED. |
| to | The recipient's phone number, E.164. |
| from | Your HonkIO number the message was sent from, E.164. |
| error_code | HonkIO's error code for the failure, the same one the message shows in the API. |
| error_message | A readable explanation of error_code. |
| errors | What the carrier reported, as a list; empty when it reported nothing. |
| errors[].code | The carrier's error code, as given. |
| errors[].title | A short description of the error. |
| errors[].detail | More 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.
{
"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 field | Meaning |
|---|---|
| message_id | The HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages. |
| status | The carrier's own status from its receipt, lower case (for example delivered, sending_failed, delivery_failed or expired). |
| message_status | HonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED. |
| to | The recipient's phone number, E.164. |
| from | Your HonkIO number the message was sent from, E.164. |
| errors | What the carrier reported, as a list; empty when it reported nothing. |
| errors[].code | The carrier's error code, as given. |
| errors[].title | A short description of the error. |
| errors[].detail | More 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.
{
"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 field | Meaning |
|---|---|
| message_id | The HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages. |
| from | The phone number that texted you, E.164. |
| to | Your HonkIO number that received the text, E.164. |
| body | The text of the message. |
| keyword_action | What 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_count | How many SMS segments the message was split into, which is what it was billed per. |
| cost_cents | What receiving the message cost you, in Canadian cents. |
| message_status | HonkIO'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.
{
"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 field | Meaning |
|---|---|
| message_id | The HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages. |
| from | Your HonkIO number the message was sent from, E.164. |
| to | The recipient's phone number, E.164. |
| status | The message status at this point, upper case (the same value as message_status). |
| message_status | HonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED. |
| details.cancelled_at | When the message was cancelled, ISO 8601 in UTC. |
message.rescheduled
A scheduled message was moved to a new time.
{
"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 field | Meaning |
|---|---|
| message_id | The HonkIO message id, as returned by POST /v1/messages or listed by GET /v1/messages. |
| from | Your HonkIO number the message was sent from, E.164. |
| to | The recipient's phone number, E.164. |
| status | The message status at this point, upper case (the same value as message_status). |
| message_status | HonkIO's status for the message, upper case: QUEUED, SENDING, DELIVERED, FAILED, UNDELIVERED, RECEIVED, SCHEDULED or CANCELLED. |
| details.previous_scheduled_at | The time it was scheduled for before, ISO 8601 in UTC. |
| details.scheduled_at | The 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.
{
"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 field | Meaning |
|---|---|
| phone_number | The subscriber who texted the keyword, E.164: the person to stop or resume messaging. |
| from_number | Your HonkIO number that received the keyword, E.164. An opt out applies to messages from this number. |
| keyword | The 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.
{
"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 field | Meaning |
|---|---|
| phone_number | The subscriber who texted the keyword, E.164: the person to stop or resume messaging. |
| from_number | Your HonkIO number that received the keyword, E.164. An opt out applies to messages from this number. |
| keyword | The subscriber's reply, trimmed and upper case, e.g. START. The whole reply, not only the keyword: it was recognised by its first word. |
consent.double_opt_in_confirmed
A recipient replied YES (or Y, OUI, O) to an opt-in confirmation you sent from a toll-free number, so toll-free messages to them are now allowed. The reply also fires message.received.
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"type": "consent.double_opt_in_confirmed",
"created": "2026-09-24T12:00:00.000Z",
"account_id": "clxxxaccountxxxxxxxxxxxxx",
"livemode": true,
"data": {
"recipient": "+1613XXXXXXX",
"from": "+1833XXXXXXX",
"confirmation_id": "oic_xxxxxxxxxxxxxxxxxxxxxxxx",
"confirmed_at": "2026-09-24T12:00:00.000Z"
}
}| data field | Meaning |
|---|---|
| recipient | The recipient who confirmed, E.164: toll-free messages to this number are now allowed. |
| from | Your toll-free number that sent the confirmation and received the reply, E.164. |
| confirmation_id | The opt-in confirmation id (oic_...), as returned by POST /v1/compliance/opt-in-confirmations. |
| confirmed_at | When the reply arrived, ISO 8601 UTC. |
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.
{
"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 field | Meaning |
|---|---|
| phone_number | Your HonkIO number, E.164. |
| reason | Always insufficient_balance: the month's rent could not be charged. |
| monthly_cost_cents | The number's monthly rent, in Canadian cents. |
phone_number.released
The number has left your account and cannot be recovered. Stop routing to it.
{
"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 field | Meaning |
|---|---|
| phone_number | Your HonkIO number, E.164. |
| reason | suspended_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_cents | Rent 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.
{
"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 field | Meaning |
|---|---|
| verification_id | The toll-free verification application id (tfv_...), as returned by POST /v1/toll-free-verifications. |
| status | The application’s status after the decision: CHANGES_REQUESTED, APPROVED or REJECTED, matching the event. |
| phone_numbers | The toll-free numbers the application covers. |
| phone_numbers[].id | The phone number id, as GET /v1/phone-numbers lists it. |
| phone_numbers[].phone_number | The toll-free number, E.164. |
| reviewer_note | What 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.
{
"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 field | Meaning |
|---|---|
| verification_id | The toll-free verification application id (tfv_...), as returned by POST /v1/toll-free-verifications. |
| status | The application’s status after the decision: CHANGES_REQUESTED, APPROVED or REJECTED, matching the event. |
| phone_numbers | The toll-free numbers the application covers. |
| phone_numbers[].id | The phone number id, as GET /v1/phone-numbers lists it. |
| phone_numbers[].phone_number | The 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.
{
"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 field | Meaning |
|---|---|
| verification_id | The toll-free verification application id (tfv_...), as returned by POST /v1/toll-free-verifications. |
| status | The application’s status after the decision: CHANGES_REQUESTED, APPROVED or REJECTED, matching the event. |
| phone_numbers | The toll-free numbers the application covers. |
| phone_numbers[].id | The phone number id, as GET /v1/phone-numbers lists it. |
| phone_numbers[].phone_number | The toll-free number, E.164. |
| reason | The 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.
{
"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 field | Meaning |
|---|---|
| failure_rate_pct | The share of the sample that failed, in percent, rounded. |
| failed | How many messages in the sample failed at the carrier. |
| sample | How 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.
{
"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 field | Meaning |
|---|---|
| channel | Which sending stopped: sms or email. The other channel is not paused. |
| paused_until | When live sending resumes on its own, ISO 8601 in UTC. |
| reason | Why: OPT_OUT_RATE or FAILURE_RATE for SMS; BOUNCE_RATE or COMPLAINT_RATE for email. |
| bounce_rate_pct | Email pauses for BOUNCE_RATE only: your recent live bounce rate, in percent. |
| complaint_rate_pct | Email 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.
{
"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 field | Meaning |
|---|---|
| spent_last_hour_cents | Your live spend in the last hour, in Canadian cents. |
| typical_daily_cents | What 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_cents | The 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.
{
"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 field | Meaning |
|---|---|
| limit | Which 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). |
| cap | The daily cap that was hit: your account's own override, or the platform default. |
| window_hours | The rolling window the cap counts over, in hours. Always 24. |
| received_in_window | How 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_enabled | Whether 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.
{
"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 field | Meaning |
|---|---|
| cap | The daily cap that was hit: your account's own override, or the platform default. |
| window_hours | The rolling window the cap counts over, in hours. Always 24. |
| received_in_window | How 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.
{
"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 field | Meaning |
|---|---|
| attempt_id | The 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. |
| trigger | What started the charge: threshold when your balance fell below your threshold, rent when it would not cover rent due within 24 hours. |
| amount_cents | The amount credited to your balance, in Canadian cents, before tax. |
| tax_cents | The sales tax added to the charge, in Canadian cents. |
| total_cents | The total charged to your card, in Canadian cents. |
| balance_cents | Your 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.
{
"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 field | Meaning |
|---|---|
| reason | Why it paused, upper case: DECLINED, AUTHENTICATION_REQUIRED, EXPIRED_CARD, CARD_REMOVED, BILLING_ADDRESS, ACCOUNT_REVIEW, ADMIN, REFUND or CHARGE_LIMIT. |
| attempt_id | The 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.
{
"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 field | Meaning |
|---|---|
| email.id | The HonkIO email id, as returned by POST /v1/emails. |
| email.from | The from address the email was sent with. |
| email.to | The recipient addresses, as a list. |
| email.subject | The subject line. |
| email.tags | The tags you set on the email, as a list of name and value pairs; empty when there are none. |
| email.status | queued, or scheduled when the email waits for scheduled_at. Absent on emails sent as part of a batch. |
| email.batch_id | Emails sent through POST /v1/emails/batch only: the batch the email belongs to. |
| email.is_commercial | Whether you marked the email as commercial (CASL), which decides the unsubscribe footer and header. |
email.sent
The email provider accepted the message for delivery.
{
"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 field | Meaning |
|---|---|
| email.id | The HonkIO email id, as returned by POST /v1/emails. |
| email.from | The from address the email was sent with. |
| email.to | The recipient addresses, as a list. |
| email.ses_message_id | The email provider's id for the message, for support requests. Present on the events the provider reports. |
| email.subject | The subject line. |
| email.tags | The 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.
{
"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 field | Meaning |
|---|---|
| email.id | The HonkIO email id, as returned by POST /v1/emails. |
| email.to | The recipient addresses, as a list. |
| email.from | The from address the email was sent with. |
| email.ses_message_id | The email provider's id for the message, for support requests. Present on the events the provider reports. |
| email.tags | The tags you set on the email, as a list of name and value pairs; empty when there are none. |
| details.delivered_at | When the receiving server accepted the message, ISO 8601 in UTC. |
| details.smtp_response | The receiving server's SMTP answer, as given. |
| details.processing_time_ms | How long delivery took from acceptance, in milliseconds. |
| details.recipients | The recipient addresses this report covers. |
| details.ses_message_id | The 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.
{
"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 field | Meaning |
|---|---|
| email.id | The HonkIO email id, as returned by POST /v1/emails. |
| email.to | The recipient addresses, as a list. |
| email.from | The from address the email was sent with. |
| email.ses_message_id | The email provider's id for the message, for support requests. Present on the events the provider reports. |
| email.tags | The tags you set on the email, as a list of name and value pairs; empty when there are none. |
| details.delay_type | The kind of delay the provider reported, e.g. MailboxFull, SpamDetected or TransientCommunicationFailure. |
| details.expiration_time | When the provider will stop retrying and bounce the message, ISO 8601 in UTC. |
| details.recipients | The recipients whose delivery is delayed, one object each. |
| details.recipients[].email_address | A delayed recipient's address. |
| details.recipients[].status | The SMTP status code for that recipient. |
| details.recipients[].diagnostic_code | The receiving server's own explanation, when it gave one. |
| details.ses_message_id | The same provider id as email.ses_message_id. |
email.bounced
The message bounced. A permanent bounce suppresses the address; a transient one does not.
{
"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 field | Meaning |
|---|---|
| email.id | The HonkIO email id, as returned by POST /v1/emails. |
| email.to | The recipient addresses, as a list. |
| email.from | The from address the email was sent with. |
| email.ses_message_id | The email provider's id for the message, for support requests. Present on the events the provider reports. |
| email.tags | The tags you set on the email, as a list of name and value pairs; empty when there are none. |
| details.bounce_type | Permanent, Transient or Undetermined. |
| details.bounce_subtype | The provider's finer classification, e.g. General, NoEmail or MailboxFull. |
| details.diagnostic_code | The receiving server's own explanation for the first bounced recipient, when it gave one. |
| details.suppressed | true when the address was added to your suppression list (every permanent bounce). |
| details.transient | true for a temporary bounce, which does not suppress the address. |
| details.recipients | The recipient addresses this report covers. |
| details.ses_message_id | The 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.
{
"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 field | Meaning |
|---|---|
| email.id | The HonkIO email id, as returned by POST /v1/emails. |
| email.to | The recipient addresses, as a list. |
| email.from | The from address the email was sent with. |
| email.ses_message_id | The email provider's id for the message, for support requests. Present on the events the provider reports. |
| email.tags | The tags you set on the email, as a list of name and value pairs; empty when there are none. |
| details.complained_at | When the complaint was made, ISO 8601 in UTC. |
| details.recipients | The recipient addresses this report covers. |
| details.ses_message_id | The 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.
{
"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 field | Meaning |
|---|---|
| email.id | The HonkIO email id, as returned by POST /v1/emails. |
| email.from | The from address the email was sent with. |
| email.to | The recipient addresses, as a list. |
| details.failure_code | HonkIO's code for why the email was refused or failed. |
| details.failure_message | A readable explanation of failure_code. |
| details.reason | Rejections reported by the email provider only (in place of failure_code): the provider's reason. |
| email.ses_message_id | The 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.
{
"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 field | Meaning |
|---|---|
| email.id | The HonkIO email id, as returned by POST /v1/emails. |
| email.from | The from address the email was sent with. |
| email.to | The recipient addresses, as a list. |
| details.failure_code | HonkIO's code for why the email was refused or failed. |
| details.failure_message | A 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.
{
"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 field | Meaning |
|---|---|
| email.id | The HonkIO email id, as returned by POST /v1/emails. |
| email.from | The from address the email was sent with. |
| email.to | The recipient addresses, as a list. |
| details.ua | The 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.
{
"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 field | Meaning |
|---|---|
| email.id | The HonkIO email id, as returned by POST /v1/emails. |
| email.from | The from address the email was sent with. |
| email.to | The recipient addresses, as a list. |
| details.url | The link that was followed, as written in your email. |
| details.ua | The 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.
{
"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 field | Meaning |
|---|---|
| email.id | The HonkIO email id, as returned by POST /v1/emails. |
| email.from | The from address the email was sent with. |
| email.to | The recipient addresses, as a list. |
| details.recipient | The address that unsubscribed. |
| details.from_domain | The sending domain the address is now suppressed for. |
email.cancelled
A scheduled email was cancelled before it went out, and the charge was refunded.
{
"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 field | Meaning |
|---|---|
| email.id | The HonkIO email id, as returned by POST /v1/emails. |
| email.from | The from address the email was sent with. |
| email.to | The recipient addresses, as a list. |
| details.cancelled_at | When the email was cancelled, ISO 8601 in UTC. |
email.rescheduled
A scheduled email was moved to a new time.
{
"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 field | Meaning |
|---|---|
| email.id | The HonkIO email id, as returned by POST /v1/emails. |
| email.from | The from address the email was sent with. |
| email.to | The recipient addresses, as a list. |
| email.tags | The tags you set on the email, as a list of name and value pairs; empty when there are none. |
| details.previous_scheduled_at | The time it was scheduled for before, ISO 8601 in UTC. |
| details.scheduled_at | The 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.
{
"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 field | Meaning |
|---|---|
| email.id | The received email's id (rcv_…). Fetch its body with GET /v1/emails/received/:id. |
| email.from | The sender's address, as the From header gave it. |
| email.from_name | The sender's display name, or null. |
| email.to | The To header addresses. |
| email.cc | The Cc header addresses. |
| email.subject | The subject line. |
| email.message_id | The 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_to | The Message-ID this email replies to, or null. |
| email.received_at | When the message reached us, ISO 8601 UTC. |
| email.size_bytes | The raw message size, headers included. |
| email.domain_id | The receiving domain's id, or null for your managed inbound address. |
| email.verdicts | Authentication and spam results from the receiving check. PASS, FAIL, GRAY or PROCESSING_FAILED. |
| email.verdicts.spf | SPF result for the envelope sender. |
| email.verdicts.dkim | DKIM signature result. |
| email.verdicts.dmarc | DMARC result for the From domain. |
| email.verdicts.spam | Spam scan result. Messages that scanned as a virus are never delivered. |
| email.attachments | The attachments, metadata only. Download each with GET /v1/emails/received/:id/attachments/:attachment_id within 40 days. |
| email.attachments[].id | The attachment id (rat_…). |
| email.attachments[].filename | The filename as sent, or attachment-N when it had none. |
| email.attachments[].content_type | The declared content type. |
| email.attachments[].size_bytes | Size in bytes. |
| email.attachments[].inline | true for an inline part such as an embedded image. |
email_domain.verified
A sending domain's DNS records checked out and it can send.
{
"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 field | Meaning |
|---|---|
| domain.id | The HonkIO email domain id. |
| domain.domain | The domain name. |
| domain.region | The 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.
{
"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 field | Meaning |
|---|---|
| domain.id | The HonkIO email domain id. |
| domain.domain | The domain name. |
| domain.status | failed 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_reason | Why verification failed, e.g. which records were not found. |
| details.revalidation | true 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.
{
"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 field | Meaning |
|---|---|
| domain.id | The HonkIO email domain id. |
| domain.domain | The 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.
{
"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 field | Meaning |
|---|---|
| domain.id | The HonkIO email domain id. |
| domain.domain | The domain name. |
| domain.status | Always 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_reason | Why verification failed, e.g. which records were not found. |
| details.revalidation | true 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.
{
"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 field | Meaning |
|---|---|
| id | The notification id, as returned by POST /v1/notify and read by GET /v1/notifications/:id. |
| user_id | The user_id the notification was sent to: your id for the recipient. |
| status | The notification's final status, lower case: delivered or failed, matching the event. |
| channel | The channel that delivered it, one of the channels in attempts; null when none did. |
| message_id | The id of the SMS that delivered it, as GET /v1/messages lists it; null unless channel is sms. |
| email_id | The id of the email that delivered it, as GET /v1/emails lists it; null unless channel is email. |
| push_id | The id of the push that delivered it; null unless channel is push. |
| attempts | The channels tried, in order: up to the one that delivered it, or every channel when none did. |
| attempts[].channel | The channel tried. |
| attempts[].outcome | What 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[].reason | Why, 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. |
| livemode | true 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.
{
"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 field | Meaning |
|---|---|
| id | The notification id, as returned by POST /v1/notify and read by GET /v1/notifications/:id. |
| user_id | The user_id the notification was sent to: your id for the recipient. |
| status | The notification's final status, lower case: delivered or failed, matching the event. |
| channel | The channel that delivered it, one of the channels in attempts; null when none did. |
| message_id | The id of the SMS that delivered it, as GET /v1/messages lists it; null unless channel is sms. |
| email_id | The id of the email that delivered it, as GET /v1/emails lists it; null unless channel is email. |
| push_id | The id of the push that delivered it; null unless channel is push. |
| attempts | The channels tried, in order: up to the one that delivered it, or every channel when none did. |
| attempts[].channel | The channel tried. |
| attempts[].outcome | What 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[].reason | Why, 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. |
| livemode | true for a notification sent with a live key, false for a test key, as in the envelope. |
HonkIO