Overview
If your platform automates outbound messages for your clients — on WhatsApp, Messenger, Instagram, SMS, or any other text channel — Encore can supply one extra outbound message per conversation: a complimentary, brand-safe offer (Apple Music free trial, Disney+ trial, etc.) sent alongside the response your client already sends.
The integration is one REST call; no SDK to install, no per-message webhook to host. You pass us just the conversation identifier and three targeting attributes. We return the offer’s link + context fields — a branded short clickUrl plus title, description, imageUrl, advertiserName, and the campaign perk. We don’t compose a message for you: you build the reply from these fields and send it as a text message through whatever messaging API you already use.
When to call us
Two triggers, both gated by a max-ads-per-thread check on your side:
- First message of a new conversation thread. Always eligible. Call Encore, send the offer message first, then your client’s normal automated reply as a follow-up.
- First message after a long idle gap (a “re-engagement” — the diner went quiet for some hours/days and returned). Also eligible, but only if you haven’t already shown the configured max number of Encore offers in the same thread.
Counting prior offers is simple: every Encore offer link is a branded short link on the link.encorekit.com host. Scan the existing message history in the thread for outbound links to that host and compare to your per-thread cap (e.g. 3). If you’re below the cap, call us; if at the cap, skip.
Sequence matters: send the offer message before your client’s reply, not after. The offer reads as a brief “while you wait” footer to the conversation rather than a postscript stapled onto the end.
When not to call
- Mid-thread replies — only first-of-thread and post-idle re-engagement triggers should fire an offer.
- The per-thread cap has been reached (counted via
link.encorekit.com links in the message history).
- The end user has opted out (out of scope at v1; recipient-level cooldown is a planned feature).
Authentication
Pass your publishable key in the X-API-Key header. That’s the only auth header required.
Use pk_test_... against the sandbox project, pk_live_... against production. Same shape as our iOS / Android / Web SDKs.
Request
The request is minimal — just the conversation identity and three targeting attributes.
POST https://api.encorekit.com/encore/publisher/sdk/v1/offers/message
Content-Type: application/json
{
"clientId": "client-42",
"chatId": "8b3f7c10-9d4e-4d2a-91a1-3f0e8a1c0b5d",
"attributes": {
"countryCode": "US",
"language": "en",
"platform": "ios"
}
}
Try it with curl
Drop in your publishable key and run.
curl --location --request POST 'https://api.encorekit.com/encore/publisher/sdk/v1/offers/message' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"clientId": "client-42",
"chatId": "8b3f7c10-9d4e-4d2a-91a1-3f0e8a1c0b5d",
"attributes": {
"countryCode": "US",
"language": "en",
"platform": "ios"
}
}'
| Field | Type | Required | Notes |
|---|
clientId | string | ✓ | Stable per-operator identifier — the business, account, or operator your platform is sending on behalf of (e.g. a restaurant ID). Carried through as an analytics dimension so impressions, clicks, and conversions can be broken down per client. |
chatId | string | ✓ | Conversation / thread identifier on the messaging platform. Used as the messaging-surface userId — drives bandit seeding, the click-time transaction, and the userId dimension on the click event. |
attributes.countryCode | ISO-3166 alpha-2 | ✓ | Two uppercase letters (e.g. "US", "GB", "CA"). Drives geo-targeting and the country dimension on analytics. |
attributes.language | string | ✓ | BCP-47 language tag (e.g. "en", "es"). Drives creative locale resolution. |
attributes.platform | "android" | "ios" | "web" | ✓ | The end user’s device platform — drives creative compatibility filtering. |
If you integrate on a Meta channel, both required fields map to values Meta puts in the inbound webhook payload — you don’t need to invent or join anything. Per platform:
| Encore field | WhatsApp Cloud API | Instagram (Messenger Platform) | Facebook (Messenger Platform) |
|---|
chatId | entry[].changes[].value.metadata.phone_number_id or your own thread key | messaging[].thread.id if present, else messaging[].sender.id | messaging[].thread.id if present, else messaging[].sender.id |
clientId | Integrator-defined (your operator’s stable ID — restaurant ID, account ID, etc.). Same value across platforms for the same operator. | | |
attributes.countryCode | Your platform’s view of the end user’s country (account profile, IP lookup, etc.). | | |
attributes.language | Your platform’s view of the end user’s language preference, or the language they’re actively chatting in. | | |
attributes.platform | Your platform’s view of the end user’s device, if known (otherwise pick the most likely surface). | | |
Response
The response returns the offer’s link + context fields — the branded short clickUrl plus title, description, imageUrl, advertiserName, and perk. There is no channel-specific message payload and no server-composed message to forward; you compose and send the message yourself (see Sending the message).
{
"success": true,
"offer": {
"clickUrl": "https://link.encorekit.com/aB3xY7q2",
"title": "Free Apple Music for 6 months",
"description": "Songs & Podcasts",
"imageUrl": "https://storage.googleapis.com/encore-assets-prod/creatives/2c643fc5-91cb-4b0a-ba0a-0536c2c4f871/primary-1778695603093.png",
"advertiserName": "Apple Music",
"perk": "6 months free"
}
}
The response carries no internal identifiers (impressionId, offer.id, campaignId, creativeId, and requestId are not returned — they’re still computed server-side for analytics) and no suggestedMessage or ctaText. You build the message yourself from the fields below.
| Field | Type | Notes |
|---|
success | true | Always true on a 200. Non-success outcomes come back as 4xx/5xx; see Status codes. |
offer | object | null | null when no eligible offer was found for this user (geo-targeted out, no active campaigns, all creatives filtered by platform). See below. |
offer.clickUrl | URL | Branded short link on the fixed host link.encorekit.com. A plain URL — embed it as text in your message; it tracks the click and redirects to the advertiser’s landing page. End-user-visible. Always include it in the message you send. |
offer.title | string | Headline of the offer. Use as the lead line if you compose your own message. |
offer.description | string | null | Short supporting line about the offer (e.g. "Songs & Podcasts" for Apple Music). May be null. |
offer.imageUrl | URL | Creative image. Attach it as a media message if your channel supports media; otherwise omit — a text-only message works fine. |
offer.advertiserName | string | The advertiser / brand name (e.g. "Apple Music"). |
offer.perk | string | null | The campaign value proposition (e.g. "6 months free"), or null. Use it to describe the deal in the message you compose. |
When there’s no eligible offer
{
"success": true,
"offer": null
}
Never a 404. If offer is null, skip the promo step entirely and send only your client’s normal automated reply. Common reasons: geo-targeted out, your client has no eligible campaigns, all eligible creatives were filtered by recipient platform.
Sending the message
You send the offer to the end user yourself, as a normal text message, through whatever messaging API or channel you already use. There is no Encore-specific payload or template to forward, and we don’t compose a message for you — you build the text from the offer fields.
Build the message from the fields — title, description, perk, advertiserName. The only hard requirement: the branded short clickUrl must appear in the text so the tap attributes correctly.
Example message
A simple, effective plain-text message for raw-text channels:
Get {perk} by claiming now: {clickUrl} — Unlocked by our partner EncoreKit
→ e.g. Get 6 months free by claiming now: https://link.encorekit.com/aB3xY7q2 — Unlocked by our partner EncoreKit
This is a recommendation, not a required format — lead with title, attach imageUrl, or match your client’s tone however you like.
Recommended — append the partner sign-off. End the message you send with Unlocked by our partner EncoreKit. The clickUrl points to the link.encorekit.com host — one the end user wasn’t expecting from the business they were chatting with — so naming the partner keeps them comfortable tapping the link.
clickUrl is a branded short link on the fixed host link.encorekit.com. It’s a plain URL, so it renders and links correctly in any text channel — WhatsApp, Messenger, Instagram, SMS, RCS, etc. — with no interactive template or Graph API body required.
Attaching the image (optional)
If your channel supports media messages, you can attach offer.imageUrl as the message image and put your composed copy in the caption. If your channel is text-only, omit the image — the message still works as plain text.
Status codes
| Code | Meaning |
|---|
200 | Success. offer may be null if no eligible offer. |
400 | Malformed request (missing required field, invalid attributes.countryCode, etc.). Don’t retry; fix the request. |
401 | Missing or invalid X-API-Key. |
409 | Your client has exceeded their trial limit (rare). |
429 | Rate-limited. Back off + retry. |
503 | Server temporarily over capacity (request queue full, dependency degraded). Retry with backoff. |
5xx (other) | Encore-side failure — proceed without the promo message, send only your client’s normal reply. |
Retry guidance
- Idempotency is on your side. The server no longer deduplicates retries — every call generates a fresh short link and analytics row. Only call
/offers/message once per inbound message you’ve decided to attach an offer to (de-dupe inbound webhooks on your side before calling).
- Use exponential backoff with jitter (e.g. 200ms → 1s → 5s, max 3 retries) for transient failures (429 / 503 / network).
- After exhausting retries, drop the call and proceed with your client’s normal reply — never degrade end-user experience because of an Encore outage.
Treat any non-200 response as a soft fallback: skip the promo step and send your client’s configured auto-reply on its own. The end-user experience should never degrade because of an Encore outage.
End-to-end example
A complete flow, from incoming message to outbound text message:
1. End user +14155551234 texts your client's business number
+14155556789 on WhatsApp. Meta delivers the webhook to your platform.
2. Your platform receives the webhook / accessibility event and decides
(per the "When to call us" rules above) that this conversation is
eligible for an Encore offer.
3. Your platform POSTs to Encore (new chat → fresh chatId):
POST /encore/publisher/sdk/v1/offers/message
X-API-Key: pk_live_abc123...
{
"clientId": "client-42",
"chatId": "8b3f7c10-9d4e-4d2a-91a1-3f0e8a1c0b5d",
"attributes": { "countryCode": "US", "language": "en", "platform": "ios" }
}
4. Encore responds with the offer's link + context fields (see Response):
{
"success": true,
"offer": {
"clickUrl": "https://link.encorekit.com/aB3xY7q2",
"title": "Free Apple Music for 6 months",
"advertiserName": "Apple Music",
"perk": "6 months free",
...
}
}
5. Your platform composes a text message from those fields (including the
clickUrl, ending with the partner sign-off) and sends it to the end user
through your own WhatsApp API as the body:
"Get 6 months free by claiming now:
https://link.encorekit.com/aB3xY7q2 — Unlocked by our partner EncoreKit"
6. Your platform then sends your client's configured auto-reply normally:
"Thanks for your message! Our team will get back to you shortly."
7. End user taps the link.encorekit.com short link → OS browser opens →
advertiser landing page → attribution chain closes.