Identifiers on WhatsApp
The WhatsApp Cloud API reports several ids for the same person, and Niadra keeps each one as its own handle type. Send every id you receive in the same event: handles that arrive together on the same event are linked when they are strong, unblocked and consistent.
With WhatsApp usernames, the sender may no longer be a phone number: it becomes a BSUID. The link between a BSUID and a phone is never assumed. It needs an explicit assertion, such as the customer typing the number, an OTP or your own records. A username is a display attribute and never identifies anyone.
Steps
1. Record each inbound message with its WhatsApp id
Use the provider message id (wamid) as the idempotency_key. The WhatsApp Cloud API retries webhooks for days, and Niadra deduplicates by that key in the database: a repeated delivery is counted as a duplicate, never stored twice. Events are ordered by occurred_at, the time the message was sent, never by arrival, so a resync that replays old messages lands them in the right place.
track() returns at once. The SDK queues the event: a message of the conversation leaves at once; other events leave in batches of up to 15 or every second. The API answers only once the batch is written, masked and sealed, and the message enters the live layer with the answer (for the first contact of a number the memory does not know yet, right after): when Marina calls at 2:07 pm, the voice agent receives it in live.
2. Link the WhatsApp id to the phone the rest of the company knows
The voice agent looks Marina up byphone_e164; your CRM knows her by its own id. When you know those belong to the same person, say so with identify(). It goes out right away: a new id joins the profile in the same response, and two profiles that already existed merge before the response.
3. Treat the thread as a conversation and let Niadra split sessions
Yourconversation_id is the thread, and a WhatsApp thread may last months. Niadra splits it into sessions: on WhatsApp, turns less than about 20 minutes apart are one session (configurable per channel). The session becomes memory after 3 minutes without turns, and a customer reply within the window reopens it and extracts it again; each reopen doubles the wait (3, 6, 12 minutes), never past the channel’s window. Each session is extracted into memory on its own (a rule decides at its end whether it goes to the model: a greeting does not, a short exchange that settles an order number or a postal code does), and billing counts a conversation once per window of activity, not once per message.
Read the context before every answer, at the level the conversation proved: the customer’s turn with verification_hint: "V1" proves V1, because it came from that number, and flush() makes sure it arrived before the first read. Inside a session the pack is pinned, so the same bytes come back and the model provider keeps the prompt prefix cached; the SDK also caches it per conversation.
4. Raise the verification level with an OTP on WhatsApp
A message from a WhatsApp number is plausible by channel. Account details, documents and payments usually need more. Send a one-time code through WhatsApp; when the customer types it back correctly, record averify with method otp_whatsapp and level V3. The level applies to this conversation only, and an OTP on WhatsApp also proves possession of that number.
verify is sent immediately and drops the cached pack of that conversation. The next read comes back with the items the policy released at V3; before that, the pack said how many were withheld in withheld, so the agent knows verifying is worth asking for. A level above the ceiling of your source comes back as the item error verification_not_allowed.
5. Send voice notes and attachments by reference
Media never travels inside an event. Reserve an upload with thesubject it belongs to, PUT the bytes to the presigned URL with exactly the upload_headers of the answer (the store refuses anything else), then send the event with media_ref and the SHA-256. The SDKs do the first two steps in upload_media() and uploadMedia(). For voice notes, send your own transcript in transcript, with stt_confidence.
6. Record your agent’s answers and the handoffs
Record outbound messages withconversation.agent() (the WhatsApp message id as idempotency_key when you have it), attendant messages with human_agent() in Python or human() in TypeScript, and internal notes with visibility="internal": the customer never saw them, and the pack marks them as such. When the conversation moves to a person, record the handoff so the measurement can tell whether the destination read the context.
Next steps
Events and the batch
idempotency, ordering and per-item errors.
Identity and verification
assertions, merges and the levels.
Voice agents
the call at 2:07 pm.
Send a batch
the full request and response.

