Inbound Messages Use Case
This page describes what happens when a debtor replies to you — by SMS, WhatsApp or email — and how to get hold of what they wrote.
Receive delivers inbound messages on two paths. A webhook notifies you within seconds and carries the metadata, but not the message body — for that it gives you a reference to fetch from the get_message endpoint. A daily file export gives you the same messages in batch and copies the bodies alongside the index, so on that path there is nothing further to fetch.
The flow
Debtor replies (SMS / WhatsApp / email)
|
v
Provider (Twilio, Mailgun, …)
|
v
+-----------------------------------------------+
| Receive Platform |
| |
| stores the raw message as .eml |
| emits event.incomingDebtorMessage.received |
| or event.incomingDebtorEmail.received |
+-----------------------------------------------+
| |
(real time) (nightly)
| |
v v
Your webhook File export to S3
payload.details.* INCOMING_MESSAGES.csv
+ incoming-messages/*.eml
| |
+-----------+------------+
|
v
GET /v1/{clientId}/get_message
?direction=inbound
|
v
the message, as .eml
Both paths carry the same identifiers, so you can use either — or both, with the file export as a reconciliation check against webhooks you may have missed.
Requirements
The two paths are enabled differently.
| Path | How to enable it |
|---|---|
| Webhook | You configure this yourself. Subscribe to event.incomingDebtorMessage.received and/or event.incomingDebtorEmail.received in the Backoffice — see the Webhooks page. The lifecycle events below are subscribed the same way. |
| File export | Request it from your Receive contact. The daily CSV export is not self-service and has to be enabled for your account before any files appear. |
Both paths also need Client API credentials if you intend to call get_message — see the Authentication Use Case.
Path 1 — The webhook
Delivered within seconds of the message arriving. The fields you need are in payload.details.
{
"payload": {
"details": {
"messageType": "event.incomingDebtorMessage.received.v1",
"providerName": "Twilio",
"messageURL": "/Operations/message/SM0f3a1b2c3d4e5f60718293a4b5c6d7e8.eml",
"requestId": "SM0f3a1b2c3d4e5f60718293a4b5c6d7e8",
"from": "+447700900123",
"to": "+447700900456",
"communicationChannel": "WHATSAPP",
"claimRef": "ABCDEF123",
"receeveClaimRef": "ABCDEF123-2026-08-24",
"accountReference": "ABCDEF123",
"parentRequestId": null
},
"messageId": "51a35d12-9145-4c97-8285-08156681e47e",
"timestamp": "2026-09-01T10:26:42.847Z"
},
"signature": "SIGNATURE"
}
Validate the signature before acting on the payload — see the Webhook Validation Use Case. The full payload for both events is in the List of Webhooks.
Path 2 — The daily file export
This path has to be switched on for you. Unlike webhook subscriptions, which you manage in the Backoffice, the CSV export is enabled per account by the Receive team — ask your contact for it before expecting files.
Once enabled, the messages received the previous day are written to your reports bucket every day:
<clientId>/reports/<date>/INCOMING_MESSAGES.csv the index
<clientId>/incoming-messages/<date>/<file>.eml the bodies
INCOMING_MESSAGES.csv has one row per message:
"Time","Claim Reference","Account Reference","Channel","From","To","Subject","Request Id","Parent Request Id","Provider","Message File"
"2026-09-01T10:26:42.847Z","ABCDEF123","ABCDEF123","WHATSAPP","+447700900123","+447700900456","","SM0f3a1b2c3d4e5f60718293a4b5c6d7e8","","Twilio","incoming-messages/2026-09-01/SM0f3a1b2c3d4e5f60718293a4b5c6d7e8.eml"
"2026-09-01T07:03:20.308Z","ABCDEF124","ABCDEF124","EMAIL","debtor@example.com","","Re: Your outstanding balance","a1b2c3d4-5e6f-4071-8293-a4b5c6d7e8f9@mail.example.com","20260901070057.e3091a69c04804bf@mail.example.com","Mailgun","incoming-messages/2026-09-01/a1b2c3d4-5e6f-4071-8293-a4b5c6d7e8f9@mail.example.com.eml"
| Column | Notes |
|---|---|
Time | When the message was received, UTC |
Claim Reference | Your claim reference. May be empty — see Matching a message to a claim |
Account Reference | Your account reference, when the message was matched to one |
Channel | SMS, WHATSAPP or EMAIL |
From | The debtor: a phone number for SMS and WhatsApp, an email address for email |
To | The number the message was sent to. Empty for email |
Subject | Email only |
Request Id | The provider's reference for this message — its Message-ID for email |
Parent Request Id | The message being replied to, when it is a reply |
Provider | Who delivered it, for example Twilio or Mailgun |
Message File | Path of the .eml within the export, relative to your client prefix |
Message File points at a file delivered alongside the CSV, so for the file export you do not need to call the API at all — the body is already there.
A value beginning with ' in a text column is a deliberate escape. Spreadsheet software interprets a leading =, +, - or @ as a formula, so those characters are prefixed with an apostrophe. Strip it if you are parsing programmatically.
Retrieving the message with the API
GET /v1/{clientId}/get_message?messageURL={messageURL}&direction=inbound
Authorization: {authToken}
| Parameter | Notes |
|---|---|
messageURL | Path of the message, relative to your client prefix — see The messageURL parameter |
direction | inbound for a message the debtor sent you. Defaults to outbound, which returns messages you sent — omit it and the request succeeds but returns nothing |
Request — retrieving the WhatsApp message from the webhook above:
GET /v1/{clientId}/get_message?messageURL=/Operations/message/SM0f3a1b2c3d4e5f60718293a4b5c6d7e8.eml&direction=inbound
Authorization: {authToken}
Response — 200 OK, Content-Type: message/rfc822
Message-ID: <74873cb3-9999-6e3f-09fc-b5abc401fc55@localhost>
Content-Transfer-Encoding: 7bit
Date: Tue, 01 Sep 2026 10:26:36 +0000
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
I have paid this already
The response is the raw stored message, so an email reply arrives as a full MIME document with its headers, any HTML part and any attachments.
The messageURL parameter
Both events give you messageURL as a path relative to your client prefix, beginning with / and ending in .eml — exactly what get_message expects. Pass it through unchanged.
/Operations/message/SM0f3a1b2c3d4e5f60718293a4b5c6d7e8.eml an SMS or WhatsApp message
/Operations/email/a1b2c3d4-5e6f-4071-8293-a4b5c6d7e8f9.eml an email
The CSV's Message File column is not this value. The export keeps only the filename and
writes it under its own folder, so /Operations/message/SM0f3a....eml is listed as
incoming-messages/<date>/SM0f3a....eml. Use messageURL for get_message and Message File
for the copy delivered with the export; they are not interchangeable.
URL-encode it when building the query string. Email Message-IDs routinely contain +, = and @, and an unencoded + is read as a space.
What happens after it arrives
Receiving the message is not the end of its story. Our agents work the same queue you are mirroring: they open communications, put them back, dismiss the ones needing no reply, and attribute the ones that arrived without a claim. Four webhooks report those changes, and you subscribe to them exactly as you do the received pair.
| Event | What it means |
|---|---|
event.incomingDebtorMessage.linked | Attributed to an account, or re-attributed to a different one. This is what corrects a message that arrived with no claim |
event.incomingDebtorMessage.dismissed | Dealt with; needs no reply and has left our queue |
event.incomingDebtorMessage.read | An agent opened it |
event.incomingDebtorMessage.unread | An agent put it back in the queue |
Despite the incomingDebtorMessage name, all four cover inbound email as well as SMS and WhatsApp — they share one communication queue.
Three things to know before you build on them:
- Correlate on
requestId. Except forlinked, these events usually arrive withaccountReference,claimRefandreceeveClaimRefset tonull— the action does not itself attribute the communication. MatchrequestIdagainst thereceivedevent you already stored. - They are a stream, not a verdict. The same
requestIdcan appear many times across days, and a message received on one day may be linked three days later. Keep the latest rather than expecting a final answer, and do not expect an earlier daily file to be corrected — files are never restated. readandunreadbelong together. Subscribing to one without the other leaves your mirrored read state able to move in only one direction.
Matching a message to a claim
claimRef and receeveClaimRef tell you which claim the message belongs to. They are not always populated:
- Inbound messages (SMS, WhatsApp) are matched by the debtor's phone number, so they normally carry a claim reference.
- Inbound email is matched from the headers of the message being replied to. A reply carries
parentRequestIdand usually a claim reference; an email that starts a new thread arrives withclaimRefandreceeveClaimRefset tonull.
When there is no claim reference, parentRequestId still lets you relate the reply to the message you originally sent, whose requestId you received in the corresponding outbound communication webhook.
An unmatched message may be linked to a claim later by an agent, and that link can be changed afterwards. The received webhook and the file export both describe the message as it arrived, and neither is restated afterwards. To hear about a link made later, subscribe to event.incomingDebtorMessage.linked — see What happens after it arrives.
Notes
- A message that does not exist returns
404. A200carries the message itself, so you can treat the response body as content without checking its length. - The webhook is the real-time path and the file export is the batch one. They carry the same
requestId, so the file can be used to reconcile against webhooks you did not receive.