Skip to content

API reference

Every endpoint in the WhatsApp API.

Sessions, the unified send and campaigns, chats and inbox, contacts and presence, groups, communities, channels, labels, media, business, calls, webhooks and events, with the status timeline, the event catalogue and the error codes you will actually meet.

Base URL https://api.walayer.com/v1Auth Authorization: Bearer wsk_live_…
01

Quickstart

Send a message.

Authenticate with a scoped API key, target a session, and pass an Idempotency-Key. The response is the accepted message at status “queued”. The delivery timeline arrives over webhooks.

import { WALayer } from '@walayer/sdk';

const wa = new WALayer(process.env.WALAYER_API_KEY);

const msg = await wa.messages.send({
  sessionId: 'sess_01j...',
  to: '+15551234567',
  type: 'text',
  body: { text: 'Hello from WALayer' },
}, { idempotencyKey: crypto.randomUUID() });

console.log(msg.status); // 'queued'
02

Route surface

The full endpoint list.

Every WhatsApp action the API exposes: link and manage numbers, send and receive all 22 message types, run campaigns, work with chats, contacts, groups, communities, channels and labels, handle media, and subscribe to signed webhooks.

Sessions

A session is one connected WhatsApp number, linked by QR or pairing code. Your phone and WhatsApp Web keep working while the API drives it.

  • POST/v1/sessionsCreate a session
  • GET/v1/sessionsList sessions
  • GET/v1/sessions/:idStatus, health, warmup stage and trust score
  • PATCH/v1/sessions/:idUpdate label, pacing overrides, caps, warmup stage
  • DELETE/v1/sessions/:idGraceful logout and credential shred
  • POST/v1/sessions/:id/pairBegin linking: { "method": "qr" | "code", "phone_e164"? }
  • GET/v1/sessions/:id/pair/streamSSE: QR frames, pairing code, connected. Add ?method=code&phone= to link without scanning
  • GET/v1/sessions/:id/healthTrust score, warmup stage, delivery stats
  • GET/v1/sessions/:id/settingsThe settable session config
  • POST/v1/sessions/:id/settings/resetReset settings to plan defaults
  • GET/v1/sessions/:id/limitsCurrent send caps and warmup limits
  • POST/v1/sessions/:id/logoutLog the companion device out upstream

Messages & campaigns

One endpoint sends all 22 types to people, groups and channels. Every send takes an Idempotency-Key, which is what makes a retry on your side safe. Act on a sent message (star, pin, receipts), or publish a story.

  • POST/v1/sessions/:id/messagesUnified send: all 22 message types
  • POST/v1/sessions/:id/messages/bulkCampaign send, warmup-gated, returns a batch id
  • POST/v1/sessions/:id/storiesPublish a status / story (text, image, video, audio)
  • GET/v1/messagesList and filter by status, session, direction, date; cursor-paginated
  • GET/v1/messages/:idStatus and the full delivery timeline
  • GET/v1/messages/:id/receiptsDelivery and read receipts for a message
  • POST/v1/messages/:id/resendExplicit resend of an undelivered or failed message
  • POST/v1/messages/:id/starStar or unstar a message
  • POST/v1/messages/:id/pinPin or unpin a message (24h / 7d / 30d)
  • POST/v1/messages/:id/readSend a read receipt for an inbound message
  • POST/v1/messages/:id/playedSend a played receipt for a voice note
  • GET/v1/batchesList campaigns with progress
  • GET/v1/batches/:idProgress for one campaign

Chats & inbox

Read the conversation list and history, then manage a chat the way a person would: archive, pin, mute, mark unread, set a disappearing timer, or send typing presence.

  • GET/v1/sessions/:id/chatsThe conversation list, newest first
  • GET/v1/sessions/:id/chats/:jidOne chat with its state
  • GET/v1/sessions/:id/chats/:jid/messagesChat history
  • POST/v1/sessions/:id/chats/:jid/readMark a chat read
  • POST/v1/sessions/:id/chats/:jid/archiveArchive or unarchive a chat
  • PATCH/v1/sessions/:id/chats/:jidPin, mute, mark unread, disappearing timer
  • POST/v1/sessions/:id/chats/:jid/presenceSend typing / recording presence
  • DELETE/v1/sessions/:id/chats/:jidDelete a chat

Contacts, identity & presence

Check numbers before you message them (a classic spam fingerprint is messaging numbers that are not on WhatsApp), resolve LID ↔ phone, block or unblock, read a profile, and subscribe to presence. Rate-limited by design.

  • GET/v1/contacts/:phone/existsOn-WhatsApp check for one number
  • POST/v1/sessions/:id/on-whatsappBatch on-WhatsApp check, capped at 50 numbers
  • POST/v1/sessions/:id/contacts/checkBatch on-WhatsApp check (alias)
  • POST/v1/sessions/:id/contacts/resolveResolve LID ↔ phone JID
  • GET/v1/sessions/:id/contactsThe number's WhatsApp contacts
  • POST/v1/sessions/:id/contacts/:jid/blockBlock a contact
  • POST/v1/sessions/:id/contacts/:jid/unblockUnblock a contact
  • GET/v1/sessions/:id/blocklistRecorded and live blocklist
  • GET/v1/sessions/:id/contacts/:jid/profileA contact's profile
  • GET/v1/sessions/:id/contacts/:jid/presenceLast observed presence
  • POST/v1/sessions/:id/contacts/:jid/presence/subscribeSubscribe to a contact's presence
  • GET/v1/sessions/:id/profileThe linked number's own profile
  • PATCH/v1/sessions/:id/profileUpdate push name and about text
  • POST/v1/sessions/:id/presenceSet own online / offline presence

Groups

Create and administer groups: roster changes, invite links, the icon, admin settings, and join-request review. WhatsApp answers per participant, and so do we.

  • POST/v1/sessions/:id/groupsCreate a group
  • GET/v1/sessions/:id/groupsList groups
  • GET/v1/sessions/:id/groups/:gidGroup metadata
  • PATCH/v1/sessions/:id/groups/:gidUpdate subject or description
  • POST/v1/sessions/:id/groups/:gid/participantsAdd, remove, promote or demote
  • GET/v1/sessions/:id/groups/:gid/inviteGet the invite link
  • DELETE/v1/sessions/:id/groups/:gid/inviteRevoke and re-issue the invite link
  • POST/v1/sessions/:id/groups/:gid/invite/sendSend the invite link as a message
  • GET/v1/sessions/:id/groups/invite/:codeGroup metadata from an invite code
  • PUT/v1/sessions/:id/groups/:gid/iconSet the group icon
  • DELETE/v1/sessions/:id/groups/:gid/iconRemove the group icon
  • PATCH/v1/sessions/:id/groups/:gid/settingsAnnounce-only, locked, join approval, member-add
  • GET/v1/sessions/:id/groups/:gid/requestsPending join requests
  • POST/v1/sessions/:id/groups/:gid/requestsApprove or reject join requests
  • POST/v1/sessions/:id/groups/:gid/leaveLeave a group

Communities

Communities are parent groups with linked subgroups. Create one, link or unlink groups, create a group inside it, and manage participants across the graph.

  • GET/v1/sessions/:id/communitiesList communities
  • POST/v1/sessions/:id/communitiesCreate a community
  • GET/v1/sessions/:id/communities/:cidCommunity metadata
  • PATCH/v1/sessions/:id/communities/:cidUpdate name or description
  • DELETE/v1/sessions/:id/communities/:cidDeactivate a community
  • GET/v1/sessions/:id/communities/:cid/subgroupsList subgroups
  • POST/v1/sessions/:id/communities/:cid/subgroupsLink a group
  • POST/v1/sessions/:id/communities/:cid/groupsCreate a group in the community
  • GET/v1/sessions/:id/communities/:cid/participantsList participants
  • POST/v1/sessions/:id/communities/:cid/participantsAdd, remove, promote or demote

Channels & newsletters

Follow, create and post to WhatsApp Channels: read message history and reaction/view updates, react by server id, mark viewed, and subscribe to live updates.

  • GET/v1/sessions/:id/channelsChannels this number follows
  • POST/v1/sessions/:id/channelsCreate a channel
  • GET/v1/sessions/:id/channels/:cidChannel metadata
  • PATCH/v1/sessions/:id/channels/:cidMute or unmute
  • POST/v1/sessions/:id/channels/:cid/subscribeFollow a channel
  • POST/v1/sessions/:id/channels/:cid/unsubscribeUnfollow a channel
  • POST/v1/sessions/:id/channels/:cid/messagesPost to the channel
  • GET/v1/sessions/:id/channels/:cid/messagesChannel message history
  • GET/v1/sessions/:id/channels/:cid/updatesReaction and view-count updates
  • POST/v1/sessions/:id/channels/:cid/trackSubscribe to live updates
  • POST/v1/sessions/:id/channels/:cid/messages/:sid/reactReact to a channel message

Labels

WhatsApp Business labels: coloured, per-number, attached to chats and messages. Create, rename, delete, and associate a chat or a specific message.

  • GET/v1/sessions/:id/labelsList labels
  • POST/v1/sessions/:id/labelsCreate a label
  • PATCH/v1/sessions/:id/labels/:lidRename or recolour
  • DELETE/v1/sessions/:id/labels/:lidDelete a label
  • GET/v1/sessions/:id/labels/:lid/associationsList associations
  • POST/v1/sessions/:id/labels/:lid/associationsLabel a chat or message
  • DELETE/v1/sessions/:id/labels/:lid/associationsRemove an association

Business, calls & bots

Read a business profile and order items, resolve a wa.me business link, reject an incoming call, and list the Meta AI bots a number can reach.

  • GET/v1/sessions/:id/business/profileA business profile
  • GET/v1/sessions/:id/business/orders/:oidItems of an order message
  • GET/v1/sessions/:id/business/link/:codeResolve a wa.me business link
  • POST/v1/sessions/:id/calls/:cid/rejectReject an incoming call
  • GET/v1/sessions/:id/botsList Meta AI bots

Media

Upload once, reference by id. Uploads are content-addressed, so sending the same file twice costs one upload. URL ingestion is SSRF-guarded.

  • POST/v1/mediaUpload by base64 or by URL
  • GET/v1/mediaThe media library (metadata)
  • GET/v1/media/:idShort-lived read URL for stored media
  • DELETE/v1/media/:idDelete media

Webhooks & events

Everything inbound arrives here: messages, receipts, status changes, presence, calls and session lifecycle. At-least-once, HMAC-signed, retried on a backoff, then dead-lettered with replay available.

  • POST/v1/webhooksRegister an endpoint (multiple per tenant or session)
  • GET/v1/webhooksList endpoints
  • PATCH/v1/webhooks/:idUpdate URL, secret or event filter
  • DELETE/v1/webhooks/:idRemove an endpoint
  • POST/v1/webhooks/:id/testFire a signed test delivery at the endpoint
  • GET/v1/eventsEvent history: ?since= to replay forward, ?order=desc to read newest-first
  • GET/v1/events/typesThe event catalogue
  • POST/v1/events/:id/redeliverManual redelivery of one event

Machine-readable surface: openapi.yaml

03

Message types

22 types, one endpoint.

The type field selects the body shape. People, groups and channels all go through the same call: text and media through to stories, live location, link previews and contact lists.

  • Text
  • Image
  • Video
  • Audio / voice note
  • Document
  • Sticker
  • Location
  • Contact card
  • Reaction
  • Poll
  • Buttons
  • List
  • Reply / quote
  • Forward
  • Revoke
  • Edit
  • Presence
  • Short video (PTV)
  • GIF
  • Link preview
  • Live location
  • Contact list
People
Direct one-to-one conversations.
Groups
Full participant and admin management, invite links, mentions.
Channels
Broadcast to newsletter subscribers.
04

Delivery timeline

What a message reports, and when.

A send is accepted, then paced and delivered. Every transition is an event you can act on.

  • queuedAccepted and durably stored. The API answers here, never with a fake “sent”.
  • dispatchedHanded to the session actor and paced for delivery.
  • sentAcknowledged by WhatsApp.
  • deliveredDelivered to the recipient's device.
  • readRead by the recipient, where read receipts are enabled.
  • unknownThe outcome is ambiguous. Never auto-retried; a reconciler resolves it.
  • failedTerminal. Carries an error code explaining why.
05

Webhooks

Verify every delivery.

Each event is HMAC-signed over the raw body with a timestamp and a delivery id. Verify the signature, enforce the replay window, and dedupe on the delivery id.

Verify a webhook
import crypto from 'node:crypto';

export function verify(req, secret) {
  const sig = req.headers['x-signature'];        // 'v1,sha256=<hex>'
  const ts  = req.headers['x-timestamp'];

  // Reject anything outside a 5-minute replay window.
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

  const expected = 'v1,sha256=' + crypto
    .createHmac('sha256', secret)
    .update(ts + '.' + req.rawBody)              // RAW body, not re-serialized
    .digest('hex');

  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

The envelope

{
  "id": "evt_6019588f-7931-4bb6-a051-d2eec2faa7e6",
  "event": "message.received",
  "session_id": "sess_15213376-035f-442e-937d-3f187be84fec",
  "timestamp": 1787302731,
  "data": {
    "…": "per-event, see below"
  }
}

timestamp is Unix seconds. id is stable across retries, so dedupe on it: delivery is at-least-once and not ordered.

x-signature
v1,sha256=<hex> over `{timestamp}.{rawBody}`
x-timestamp
Unix seconds. Signed. Reject anything older than 300s.
x-event
The event type, so you can route without parsing the body.
x-delivery-id
Changes on every retry. Dedupe on the body's `id`, not this.
05b

Webhook payloads

Every event, and exactly what it sends.

The data object of every event the platform emits today, field for field. Fields are only ever added within v1, never removed or retyped, so parse permissively and ignore what you do not recognise.

message.received

An inbound message. The highest-volume event.

In a group, chat_jid ends @g.us and sender_jid is the person who spoke, so key your contacts on the sender, never the chat. `text` is an empty string when the workspace has content storage off; the event still fires, without the words.

{
  "message_id": "msg_9c2f1a44-8e21-4a0e-9f3e-1b7c0d5a2e11",
  "wa_message_id": "3EB0F1A2C3D4E5F60718",
  "chat_jid": "[email protected]",
  "sender_jid": "[email protected]",
  "from_me": false,
  "type": "text",
  "text": "Is my order shipped?",
  "at": 1787302731
}
call.received

An incoming voice or video call. Never creates a message.

{
  "call_id": "5A1B2C3D4E5F60718293",
  "chat_jid": "[email protected]",
  "caller_jid": "[email protected]",
  "at": 1787302731
}
presence.updated

A contact's presence changed.

Only arrives for contacts you have subscribed to through the API. state is `available` or `unavailable`.

{
  "jid": "[email protected]",
  "state": "available",
  "last_seen": 1787302731
}
session.paired

A number finished linking. The activation moment.

The first event a newly connected number produces. This is the one to hang onboarding off.

{
  "at": 1787302731
}
session.history_progress

An opt-in history import is running.

Only for numbers linked with history import turned on, and capped at 30 days. Advisory — the messages are already saved before this fires. `percent` is WhatsApp's own estimate and is not monotonic across sync passes.

{
  "percent": 42,
  "chats_imported": 7,
  "messages_imported": 903,
  "sync_type": "FULL"
}
session.connected

The session reconnected.

Fires on ordinary reconnects too: a deploy, a shard move, a few seconds of network. Read it as “up now”, not as an incident.

{
  "at": 1787302731
}
session.disconnected

The socket dropped.

Fires on transient drops that heal in seconds. Do not page a human on this one directly; wait for it to stay down, or for no session.connected to follow.

{
  "at": 1787302731
}
session.logged_out

WhatsApp ended the session. Terminal.

Logged out on the phone, or the device was unlinked. Sends fail until the number is paired again.

{
  "at": 1787302731
}
abuse.flagged

Our abuse analyser raised a flag. Nothing has stopped yet.

A warning that behaviour is trending toward a freeze, not a freeze.

{
  "flag_id": "9c2f1a44-8e21-4a0e-9f3e-1b7c0d5a2e11",
  "signal": "volume_spike",
  "severity": "warning",
  "score": 9,
  "threshold": 5,
  "review_due_at": 1787389131
}
session.suspended

Sending is frozen. Every send is refused until it lifts.

Handle this one. scope `tenant` freezes every number in the workspace and arrives with session_id null. manual is true when a human applied it.

{
  "scope": "session",
  "reason": "volume_spike",
  "manual": false,
  "review_due_at": 1787389131
}
session.unsuspended

The freeze lifted; sending resumes.

When the lift came from dismissing a flag the body is instead { scope, reason: "flag_dismissed", flag_id }.

{
  "scope": "session",
  "suspension_id": "3f1e8c02-5a71-4d33-9b0e-77c1a4e2b915"
}

Reserved, not emitted yet

Accepted by the API, so you can subscribe now and start receiving them the moment they go live. Nothing emits them today, so do not build a flow that waits on one. Delivery receipts are the notable gap: they are recorded against the message and readable over REST, but do not fan out as webhooks yet.

  • session.qr
  • session.banned
  • session.pairing_code
  • message.sent
  • message.delivered
  • message.read
  • message.failed
  • message.undelivered
  • message.undecryptable
  • message.reaction
  • message.revoked
  • message.edited
  • poll.vote
  • group.participants_changed
  • group.updated
  • contact.updated
06

Errors

The codes you will actually meet.

Every error carries a stable code, a human message and a doc_url. Terminal errors are never retried on your behalf, and neither is an ambiguous send.

Session

  • SESSION_NOT_CONNECTEDThe number is not currently linked. Re-pair or wait for reconnect.
  • SESSION_LOGGED_OUTLogged out upstream: the customer removed the device.
  • SESSION_BANNEDWhatsApp has acted on the number. Sends are frozen.
  • SESSION_SUSPENDEDSuspended by abuse controls. Arrives as an event too, not only as errors.

Recipient: terminal, never retried

  • RECIPIENT_NOT_ON_WHATSAPPThe number has no WhatsApp account.
  • RECIPIENT_BLOCKEDThe recipient has blocked this number.
  • RECIPIENT_SUPPRESSEDThe recipient opted out. Enforced at the API.

Limits

  • RATE_LIMIT_APIToo many API calls. Back off and retry.
  • RATE_LIMIT_PACINGThe session's human-like pacing is saturated.
  • WARMUP_CAP_EXCEEDEDBeyond a young number's safe daily cap. Refused up front, not queued for days.
  • QUOTA_EXCEEDEDPlan entitlement exhausted.

Delivery

  • SEND_UNKNOWNThe send may or may not have landed. Never auto-retried; a blind retry is a second real message.
  • ACK_TIMEOUTNo acknowledgement within the window.
  • IDEMPOTENT_REPLAYThis Idempotency-Key was already used; the original result is returned.

Scope & engine

  • FORBIDDENThe developer API is WhatsApp actions only. CRM, billing, usage and search are dashboard-only; detail.surface = "dashboard".
  • UNSUPPORTED_FOR_ENGINEThis action is not available on the session's engine (e.g. interactive buttons/lists). Terminal, never retried.

Infrastructure & media

  • MEDIA_TOO_LARGEAbove WhatsApp's size ceiling for that type.
  • MEDIA_UNSUPPORTEDFormat cannot be sent as that message type.

Retry classification is not advisory. Terminal errors fail immediately, transient ones back off, and SEND_UNKNOWN is quarantined for a reconciler, because a blind retry is a second real message to a real person.

07

Next

Get a key and send one.

Developer quickstart · Create an API key

Skip Meta approvals. Skip message fees. Skip the dropped messages.

Free developer sandbox, ready in minutes. No approvals, no templates, no per-message fees.

3-day free trial · connect in under 5 minutes · cancel anytime.