Skip to main content

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.

PathHow to enable it
WebhookYou 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 exportRequest 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"
ColumnNotes
TimeWhen the message was received, UTC
Claim ReferenceYour claim reference. May be empty — see Matching a message to a claim
Account ReferenceYour account reference, when the message was matched to one
ChannelSMS, WHATSAPP or EMAIL
FromThe debtor: a phone number for SMS and WhatsApp, an email address for email
ToThe number the message was sent to. Empty for email
SubjectEmail only
Request IdThe provider's reference for this message — its Message-ID for email
Parent Request IdThe message being replied to, when it is a reply
ProviderWho delivered it, for example Twilio or Mailgun
Message FilePath 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}
ParameterNotes
messageURLPath of the message, relative to your client prefix — see The messageURL parameter
directioninbound 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}

Response200 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.

EventWhat it means
event.incomingDebtorMessage.linkedAttributed to an account, or re-attributed to a different one. This is what corrects a message that arrived with no claim
event.incomingDebtorMessage.dismissedDealt with; needs no reply and has left our queue
event.incomingDebtorMessage.readAn agent opened it
event.incomingDebtorMessage.unreadAn 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:

  1. Correlate on requestId. Except for linked, these events usually arrive with accountReference, claimRef and receeveClaimRef set to null — the action does not itself attribute the communication. Match requestId against the received event you already stored.
  2. They are a stream, not a verdict. The same requestId can 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.
  3. read and unread belong 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 parentRequestId and usually a claim reference; an email that starts a new thread arrives with claimRef and receeveClaimRef set to null.

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. A 200 carries 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.