Core concepts
Client and channels
Section titled “Client and channels”Your client is your account: one webhook URL, one shared token, one wallet, one team. Everything you connect belongs to it.
A channel is one connected messaging endpoint — a WhatsApp number, an
Instagram professional account or a Facebook page. Each one gets a stable id
with an nk_ prefix:
nk_dvzobn7m4h4yThat id is what you pass to every API call and what comes back in every webhook. The underlying Meta identifiers (phone number id, WABA id, IGSID, page id) are also exposed, but you never need them to operate the channel.
Meta access tokens are encrypted at rest with AES-256-GCM and never leave the server. Your integration never handles a Meta token.
One envelope for three channels
Section titled “One envelope for three channels”Inbound messages are normalised before they reach you, so your handler does not branch per platform. Every dispatch has the same two top-level keys:
{ "event": "message.received", "channel": { "id": "nk_dvzobn7m4h4y", "platform": "whatsapp" }, "contact": { "id": "5491112345678", "name": "Ana", "phone": "+5491112345678", "username": null, "avatar_url": null }, "message": { "id": "wamid.HBgNNTQ5MTEy…", "direction": "inbound", "timestamp": "2026-08-09T18:12:44.310Z", "type": "text", "content": { "text": "Do you have stock?" }, "reply_to": null }}event tells you what happened, so a single handler can switch on it.
contact.id is the counterpart’s identifier — the phone number on WhatsApp, the
IGSID on Instagram, the PSID on Messenger. Together with channel.id it is the
key of a conversation on your side. The envelope is self-contained: the
contact travels inline, so rendering “who wrote and what they said” never needs
a second request.
message.type decides the shape of message.content, and the type is never
repeated inside it. Text is { text }; attachments are
{ url, mime_type, media_id, caption? }; a reaction is
{ emoji, reacted_message_id }; a location is
{ latitude, longitude, name?, address?, map_url }. reply_to holds the quoted
message id when the customer replied to something.
Delivery model
Section titled “Delivery model”Nothing is delivered synchronously, in either direction.
Inbound. Meta calls us, we verify the HMAC signature, store media in object
storage, deduplicate, normalise, and put the result on a queue. A worker POSTs
it to your backend. If your endpoint fails or times out, the job is retried; the
outcome is recorded in an event store, so a failed dispatch can be replayed from
the console instead of being lost.
Outbound. POST /api/outbound/send answers 202 and a jobId. That means
queued, not delivered. A worker resolves the channel, checks your wallet,
decrypts the token and calls Meta. The real outcome comes back to you as a
status webhook.
Webhooks
Section titled “Webhooks”You configure one base URL. We append the path:
| What | Path |
|---|---|
| WhatsApp inbound | {base}/chat/whatsapp/receive |
| Instagram inbound | {base}/chat/instagram/receive |
| Messenger inbound | {base}/chat/facebook/receive |
| Status updates | {base}/chat/whatsapp/update |
Every request carries your shared token in the x-webhook-token header. Compare
it before processing — it is what proves the call came from us.
Authentication and scopes
Section titled “Authentication and scopes”API requests authenticate with an API key:
x-neike-api-key: whk_live_…Keys are whk_live_ plus 40 characters, stored only as a SHA-256 hash, and
shown once at creation. They can be rotated with a grace period so you migrate
without downtime, and revoked instantly.
Each key carries scopes — messages:send, messages:read,
templates:manage, channels:manage, webhooks:manage, usage:read,
billing:manage, team:manage, keys:manage, marketing:read,
marketing:manage. An endpoint declares what it needs; a key without that
scope gets 403. marketing:manage is never granted by default — it spends
the advertiser’s money, so you tick it on purpose (see
Marketing).
Console sessions authenticate differently: a bearer JWT plus x-neike-client-id
when the user belongs to more than one client.
Billing
Section titled “Billing”Outbound messages draw from a prepaid wallet, priced per Meta’s conversation
categories. With insufficient balance a send is rejected with 402 — inbound
messages keep flowing regardless, so you never lose a customer message because
of a balance.