AhaSend

Important: Two changes to message IDs in the AhaSend API and webhooks

Mark Kraakman
Mark Kraakman
Product Update
On this page

We hate breaking changes too, but to make the APIs more ergonomic and allow for easier integrations and future functionality, we are making each message's ID the same everywhere: in API send responses, in Get Message, in webhooks, in the MCP and in the dashboard. Please check whether they affect your integration.

These changes take effect on 9 November 2026.

1. The id in API send responses

The id of each recipient in the response of Create Message, Create Template Message and Create Conversation Message becomes a bare UUID. Today it is the full Message-ID header, with angle brackets and a domain.

Today:

{
  "object": "message",
  "id": "<[email protected]>",
  "recipient": { "email": "[email protected]" },
  "status": "queued"
}

From November 9th:

{
  "object": "message",
  "id": "0192f5a4-7b1c-7d3e-9a2b-4c5d6e7f8a9b",
  "message_id_header": "<[email protected]>",
  "recipient": { "email": "[email protected]" },
  "status": "queued"
}

The Message-ID header moves to a new field, message_id_header, in the same response.

What you may need to do

  • If you match a send with its webhook events by comparing the response id with the webhook's data.message_id_header, compare message_id_header instead.
  • If you filter List Messages by message_id_header using the id from a send response, use message_id_header instead.
  • Don't derive id from message_id_header, or the other way round: they are not always the same UUID. For example, all copies of one Create Conversation Message send (To, Cc and Bcc) will share one message_id_header, while each copy keeps its own id.

What keeps working

  • Get Message and Cancel Message accept both the new bare id and the old <...@domain> form. IDs you have already stored keep working.
  • The Message-ID header of your emails does not change.
  • If you repeat a request with the same Idempotency-Key, you get the stored response of the first request. For up to 24 hours after November 9, a repeat of an earlier request can still return the old form, without message_id_header.
  • Older versions of our SDKs keep working: id is still a string. New SDK versions add message_id_header.

2. The message ID in webhooks and the dashboard

Message webhooks (message.reception, message.delivered, message.transient_error, message.failed, message.bounced, message.suppressed, message.opened and message.clicked) carry the message's ID as data.id: the same id that the send response gave you and that Get Message takes. Today data.id is the an internal injection ID, a different UUID written without hyphens.

Today:

"data": {
  "id": "407926766d2711f09b30960002cafe7c",
  "message_id_header": "<[email protected]>"
}

From November 9th:

"data": {
  "id": "0192f5a4-7b1c-7d3e-9a2b-4c5d6e7f8a9b",
  "message_id_header": "<[email protected]>"
}

What you may need to do

  • If you group a message's webhook events by data.id, note that a message sent before November 9 can get events with both IDs: for example a message.delivered event with the old ID and a later message.opened event with the new one. This can happen for up to 30 days. data.message_id_header does not change, so you can group by it across the switch.
  • If you check the shape of data.id, it now has hyphens and is 36 characters long, not 32.
  • If you archive messages to your own S3 bucket, new archives use the message's ID in their folder and file names. Archives written before November 9 keep their paths.
  • Campaign messages send the same message.* events. Their data.id changes to the same 36-character format, with hyphens. Use it to group a campaign message's events. Get Message does not return individual campaign messages (it only works for Transactional messages).
  • Inbound route webhooks (message.routing) carry the same message ID as id, so it matches the data.id of that message's message.reception event.

What keeps working

  • The dashboard shows this same ID as the message's "Message ID". Dashboard links that you already shared keep working.
  • An ID you stored from an older webhook still works with Get Message and Cancel Message.

If you have questions, please reach out via [email protected] or on our Chat server.