To integrate Inbox into a non-mobile system, configure a Custom Inbox application and call REST endpoints directly from your backend. This guide walks you through setup, fetching messages, and reporting events.
End-to-end integration for SDK-less surfaces. Your system pulls messages from the public endpoints, renders them in its own UI, and feeds events back through the same endpoints. The SDK paths below wrap these exact endpoints.
For an overview, see the Inbox User Guide.
1. Create a Custom Inbox application
For a system that will integrate without the SDK, first define an application of type Custom Inbox in the panel. Refer to the Inbox Application Guide.
The channel only serves accounts with the Inbox feature (inbox_enabled) switched on. On a disabled account every request is rejected with400.
2. Fetch messages
Retrieves the user's active messages, newest first. Expired and deleted messages never appear.
GET /api/inbox/getMessages
Messages for identified users are stored by ckey, messages for anonymous users by did. When a request carries both, Dengage reads both mailboxes and merges them — which is why a user who registers later keeps the messages received while anonymous. Custom Inbox requests are authenticated with the app's Public ID.
Query parameters
| Parameter | Required | Description |
|---|---|---|
acc | Yes | Account identifier (account GUID) |
did | SDK: yes | device_id — required in SDK integrations |
ckey | Custom Inbox: yes | contact_key — sent whenever available; mandatory on Custom Inbox |
appId | With ckey: yes | Application GUID — required whenever ckey is sent |
limit | No | Default 20 · min 1 · max 100 |
Response
[
{
"smsgId": "ib-8842-15",
"isRead": false,
"priority": 1,
"messageJson": {
"title": "Siparişiniz kargoda",
"message": "Siparişiniz yola çıktı, bugün teslim edilecek.",
"imageUrl": "https://cdn.example.com/inbox/order-shipped.png",
"type": "imagetext",
"isPinned": true,
"receiveDateUTC": "2026-07-10T09:15:00.000Z",
"ctaButtons": [
{
"label": "Siparişi Takip Et",
"iosDeeplink": "myapp://orders/8842",
"androidDeeplink": "myapp://orders/8842",
"webUrl": "https://shop.example.com/orders/8842",
"buttonId": "1"
}
],
"messageDetails": "eyJzbXNnSWQiOiJCLTg4NDItMTUiLCJleHAiOjE3..."
}
}
]
KeepmessageDetails. It is an opaque server token carried by every message. Every event reported for a message must echo it back verbatim, or the backend cannot attribute the interaction. Build events from the message object, not from a bare id.
Empty list ≠ error. If data is temporarily unreachable, the API returns200with an empty list instead of an error. Design your UI for this: show an empty state, not a failure state.
3. Display messages — anatomy
Message content arrives as structured data, and your UI renders it however fits your brand — which is also what lets the channel run on screenless surfaces such as IVR.
title— headline of the messagemessage— main body textimageUrl— visual content (nullable — present forimageandimagetexttypes)type—text|image|imagetextctaButtons— action buttons (nullable; every field optional):label,iosDeeplink,androidDeeplink,webUrl,buttonId. Prefer the platform deeplink, fall back towebUrl. EchobuttonIdon click.isPinned— pinned messages belong at the top of the list; ordering is your UI's responsibilitypriority— set at send time; use it to emphasize important messagesisRead— read state, set by yourOPandCLevents (IMchanges nothing)receiveDateUTC— when the message reached the Inbox (yyyy-MM-dd'T'HH:mm:ss.SSS'Z')
4. Report events
Reports the user's interactions with messages. The endpoint accepts up to 50 events per request — batch instead of firing one call per event.
POST /api/inbox/events
Batch by design. When the user opens the Inbox and 20 messages are shown, that's 20 Impression events — send them together in one request, not as 20 separate calls.
Event types
| Code | Event | Effect |
|---|---|---|
IM | Impression | Statistics only — the message was visible in the list; changes nothing on the message |
OP | Open | Marks the message as read |
CL | Click | Marks as read; include the tapped button's buttonId |
DT | Delete | Marks the message as deleted — it is no longer returned |
| custom | Custom event | Custom Inbox apps only — e.g. order approved, coupon used, call completed |
The eventType field must be one of these short codes. The SDKs send only the standard codes; on Custom Inbox apps, the custom codes registered for that app are also valid. Events from the SDK and the REST API are stored the same way and feed the same places: reports, analytics, segments, and journey conditions.
Custom event codes
Custom events are defined on the Custom Inbox application (step 1). Each code follows these rules:
- Exactly 2 letters (A–Z), automatically converted to uppercase
- Cannot be one of the standard codes (
IM,OP,CL,DT) - At most 10 active custom events per application
- The label can be edited later; the code cannot
Events with unregistered codes are rejected by the API — register your codes in the panel before go-live.
eventDateUTCrule: always send the current UTC time. Never send a past timestamp or local time. (The SDKs stamp this for you.)
Identifiers
Same rules as fetching: SDK integrations always send did and add ckey when available; an anonymous user's events are processed by did, a contact's events by ckey. Custom Inbox apps send ckey only. appId is required.
Request body
{
"acc": "90db7e2a-5839-53cd-605f-9d3ffc328e21",
"ckey": "contact-4471",
"did": "b7e2f1a0-9c34-4d21-8f6a-2e5b0c7d1a93",
"appId": "3f9a1c88-2b6e-4a70-9d15-8c4e2f6b0a12",
"events": [
{
"eventType": "CL",
"msgid": "ib-8842-15",
"messageDetails": "eyJzbXNnSWQiOiJCLTg4NDItMTUiLCJleHAiOjE3...",
"eventDateUTC": "2026-07-10T10:22:00Z",
"buttonId": "1"
},
{
"eventType": "OP",
"msgid": "ib-8842-15",
"messageDetails": "eyJzbXNnSWQiOiJCLTg4NDItMTUiLCJleHAiOjE3...",
"eventDateUTC": "2026-07-10T10:22:00Z"
},
{
"eventType": "IM",
"msgid": "ib-8811-04",
"messageDetails": "eyJzbXNnSWQiOiJCLTg4MTEtMDQiLCJleHAiOjE3...",
"eventDateUTC": "2026-07-10T10:22:00Z"
}
]
}
Response
200 OK
{ }
5. Manage state locally — the hybrid model
Event processing is asynchronous: events pass through a pipeline (collection → processing) and take a short while to fully process. Your UI must not wait for that.
Do: reflect the user's action in the local UI immediately — a deleted message disappears at once; send the event in the background; serve instant experience locally and let the server own durable correctness.
Don't: follow a "send event → wait for processing → refresh" flow — the user would see the result of their own action with a delay.
Error handling
Invalid requests are rejected with 400 and a body of { "message": "..." }.
| Case | Result |
|---|---|
Invalid limit value | 400 — Invalid Value: limit |
| Unknown account | 400 — Invalid Account |
| Unknown application | 400 — Invalid app |
Invalid eventType | 400 — Invalid Value: eventType |
| Custom event code not registered for the app | Rejected — register codes in the panel first |
| Inbox feature disabled on the account | 400 |
| Data temporarily unreachable (fetch) | 200 with an empty list — not an error |