RelayAPI

WhatsApp API

Send WhatsApp messages with RelayAPI — text messages, images, videos, documents, templates, and link previews via the WhatsApp Business Cloud API.

Quick Reference

PropertyValue
Platform keywhatsapp
Auth methodWhatsApp Business Cloud API Token
Text limit4,096 characters
Caption limit1,024 characters (media captions)
Media per message1
Image formatsJPEG, PNG
Image max size5 MB
Video formatsMP4, 3GPP
Video max size16 MB
Message typesText, Image, Video, Document, Audio, Template
AdministrationCapability-gated groups, block lists, business usernames, and template library/editing
SchedulingYes
AnalyticsNo (use WhatsApp Business Manager)

Client optionsTypeScript · Python REST/OpenAPI · Go · Java · REST API

Before You Start

WhatsApp requires a WhatsApp Business Account with access to the Cloud API (via Meta for Developers). Messages outside the 24-hour conversation window require pre-approved message templates. Every message requires a recipient phone number in E.164 format (without the + prefix). Only 1 media item per message is supported. Image, video, and document captions are limited to 1,024 characters. Audio attachments cannot include post content as a caption; RelayAPI rejects that combination before provider I/O.

WhatsApp administration and messaging require an embedded token with both whatsapp_business_management and whatsapp_business_messaging. Reconnect or repeat embedded signup for an older connection whose token lacks either grant; refreshing the existing token cannot add permissions.

Quick Start

Send a text message via WhatsApp:

import Relay from '@relayapi/sdk';

const client = new Relay();

const post = await client.posts.create({
  content: 'Hello from RelayAPI!',
  targets: ['whatsapp'],
  scheduled_at: 'now',
  target_options: {
    whatsapp: {
      to: '14155238886'
    }
  }
});

console.log(post.id); // post_abc123

Content Types

Text Message

Plain text message up to 4,096 characters.

const post = await client.posts.create({
  content: 'Hello from RelayAPI!',
  targets: ['whatsapp'],
  scheduled_at: 'now',
  target_options: {
    whatsapp: {
      to: '14155238886'
    }
  }
});

Enable URL previews in text messages.

const post = await client.posts.create({
  content: 'Check out our new feature: https://example.com/launch',
  targets: ['whatsapp'],
  scheduled_at: 'now',
  target_options: {
    whatsapp: {
      to: '14155238886',
      preview_url: true
    }
  }
});

Image Message

Send a single image with an optional caption (max 1,024 chars).

const post = await client.posts.create({
  content: 'Check out this photo!',
  targets: ['whatsapp'],
  media: [
    { url: 'https://cdn.example.com/photo.jpg', type: 'image' }
  ],
  scheduled_at: 'now',
  target_options: {
    whatsapp: {
      to: '14155238886'
    }
  }
});

Video Message

const post = await client.posts.create({
  content: 'Watch our latest update!',
  targets: ['whatsapp'],
  media: [
    { url: 'https://cdn.example.com/video.mp4', type: 'video' }
  ],
  scheduled_at: 'now',
  target_options: {
    whatsapp: {
      to: '14155238886'
    }
  }
});

Document Message

Send a file as a document attachment.

const post = await client.posts.create({
  content: 'Here is your invoice.',
  targets: ['whatsapp'],
  media: [
    { url: 'https://cdn.example.com/invoice.pdf', type: 'document' }
  ],
  scheduled_at: 'now',
  target_options: {
    whatsapp: {
      to: '14155238886'
    }
  }
});

Template Message

Use pre-approved message templates for outbound messages outside the 24-hour window.

const post = await client.posts.create({
  targets: ['whatsapp'],
  scheduled_at: 'now',
  target_options: {
    whatsapp: {
      to: '14155238886',
      template_name: 'order_confirmation',
      template_language: 'en_US',
      template_components: [
        {
          type: 'body',
          parameters: [
            { type: 'text', text: 'John' },
            { type: 'text', text: '#12345' }
          ]
        }
      ]
    }
  }
});

Templates must be pre-approved in the WhatsApp Business Manager before use. Template messages bypass the 24-hour conversation window restriction.

Media Requirements

Images

PropertyRequirement
Max per message1
FormatsJPEG, PNG
Max file size5 MB

Videos

PropertyRequirement
Max per message1
FormatsMP4, 3GPP
Max file size16 MB

Documents

PropertyRequirement
Max per message1
FormatsPDF, DOC, DOCX, PPT, PPTX, XLS, XLSX, and more
Max file size100 MB

target_options Fields

All fields go inside target_options.whatsapp on your post request.

FieldTypeDefaultDescription
contentstringOverride message content for WhatsApp specifically
mediaobject[]Override media for WhatsApp specifically
tostringRequired. Recipient phone number in E.164 format without + (e.g., "14155238886")
preview_urlbooleanfalseEnable link preview in text messages
template_namestringPre-approved template name for outbound messages
template_languagestring"en_US"Template language code
template_componentsobject[]Template parameter components

Delivery status lifecycle

The Cloud API's message-create response supplies a WhatsApp message ID and confirms acceptance, not recipient delivery. RelayAPI keeps that outbound post target nonterminal while it waits for a signed WhatsApp status callback.

Provider eventRelayAPI outcomeTerminal?
Initial message acknowledgementAccepted; message ID retained for callback correlationNo
sentAccepted by WhatsApp, but not proven delivered to the recipientNo
deliveredDeliveredYes, success
readDelivered/readYes, success
failedFailed, with Meta's error code/message when suppliedYes, failure

Status callbacks are processed only after RelayAPI verifies the raw request's X-Hub-Signature-256 with the configured Meta app secret. Callback delivery is at-least-once and can be reordered, so RelayAPI applies monotonic transitions: a duplicate or late sent/delivered event cannot regress a terminal read or failed result. A terminal callback persists the target and its publish attempt together and schedules any parent-post/thread continuation in the same fenced transaction.

If no terminal callback arrives, the target remains nonterminal; do not treat a sent event as proof of handset delivery.

Common Errors

ErrorCauseFix
Missing recipientNo to phone number providedAdd to in target_options.whatsapp with E.164 format (without +).
Content too longText exceeds 4,096 charactersShorten the content.
Empty contentNo content or media providedAdd text content or at least one media item.
Template not foundTemplate name not approved or misspelledVerify the template exists and is approved in WhatsApp Business Manager.
Outside conversation windowSending non-template message outside 24h windowUse a template message instead.

Known Quirks

  • 24-hour conversation window — you can only send free-form messages within 24 hours of the user's last message. Outside this window, use templates.
  • 1 media item per message — no multi-image or gallery support.
  • Audio messages have no caption — captions are only supported on image, video, and document messages. A publisher request that combines audio with nonblank post content or a WhatsApp content override is rejected before provider I/O.
  • Media captions limited to 1,024 characters — shorter than the 4,096-character text limit.
  • Phone numbers use E.164 format without the + — e.g., 14155238886 not +14155238886.
  • Template messages require pre-approval — submit templates in the WhatsApp Business Manager.
  • Media must be publicly accessible via HTTPS — private URLs will fail.

RelayAPI also sends provider-native read receipts for persisted inbound WhatsApp messages. See Published Edits and Social Actions.

Business administration

The authenticated /v1/whatsapp/admin surface uses the exact connected WhatsApp phone-number identity and its stored WABA. Start with a live capability probe rather than assuming every Meta account has the same rollout:

const capabilities = await client.whatsapp.admin.capabilities({
  account_id: 'acc_whatsapp123',
});

if (capabilities.capabilities.groups !== 'supported') {
  console.log(capabilities.requirements);
}

Every field uses the same conservative state vocabulary:

StateMeaning
supportedA feature-specific, non-mutating read succeeded for this exact connected account
requires_eligibilityRelayAPI implements the write/webhook feature, but no safe read can prove this account's program eligibility or app configuration
unavailableThe feature-specific read was rejected as unauthorized, forbidden, or not found, or the required WABA identity is absent
unverifiedA generic provider rejection or transient failure did not prove support either way
not_yet_availableReserved for an implementation that is not exposed yet

Groups, block lists, business usernames, and the template library each have an independent read probe. Template editing and BSUID features cannot be certified with a non-mutating account probe, so their state is eligibility-based. Treat the snapshot as implementation and account evidence, not as a promise that a later provider mutation will be accepted.

Meta currently restricts the Groups API to eligible Official Business Accounts without coexistence. RelayAPI returns WHATSAPP_GROUPS_UNAVAILABLE before a group write unless the exact Groups read probe returned supported. A non-supported state does not by itself diagnose which Meta eligibility or configuration requirement is missing.

Groups and participants

const operation = await client.whatsapp.admin.createGroup({
  account_id: 'acc_whatsapp123',
  idempotency_key: 'create-customer-group-20260810',
  subject: 'Customer launch group',
  description: 'Launch coordination',
  join_approval_mode: 'approval_required',
});

const current = await client.whatsapp.admin.getOperation(operation.id, {
  account_id: 'acc_whatsapp123',
});

Group create and delete are asynchronous at Meta. A provider acknowledgement does not prove the group is active or deleted: RelayAPI keeps a local creating/deleting projection and applies the terminal lifecycle from signed group_create/group_delete webhooks. List or retrieve groups after the provider lifecycle event to get the current projection.

CapabilityTypeScript SDK method
List, create, retrieve, update, deletelistGroups, createGroup, retrieveGroup, updateGroup, deleteGroup
Read or rotate invite linkgetInviteLink, resetInviteLink
List and approve/reject join requestslistJoinRequests, approveJoinRequests, rejectJoinRequests
Remove participantsremoveParticipants (1–8 identities)
Send text, image, video, document, audio, or templatesendGroupMessage
Pin or unpin a group messagepinGroupMessage

RelayAPI does not invent a manual-participant-add operation because Meta's official contract does not provide one. Use invite links and join approvals. Group media accepts exactly one Meta media id or public link. Pinning requires expiration_days from 1–30; unpinning must omit it.

Block lists

await client.whatsapp.admin.blockUsers({
  account_id: 'acc_whatsapp123',
  idempotency_key: 'block-abuse-batch-1',
  users: [{ user: '15550001111' }],
});

const blocked = await client.whatsapp.admin.listBlockedUsers({
  account_id: 'acc_whatsapp123',
  limit: 100,
});

Use unblockUsers() with the same input shape to remove entries. Meta can partially apply a batch, so inspect the durable operation result rather than assuming all identities changed together.

Business usernames and BSUIDs

getUsername(), setUsername(), deleteUsername(), and usernameSuggestions() expose Meta's business-username contract. Username activation is asynchronous; poll getUsername() for RESERVED or ACTIVE. RelayAPI intentionally does not subscribe to phone_number_username_update until that webhook can be resolved to one exact connected phone number without ambiguity.

WhatsApp webhook ingestion accepts phone-optional business-scoped user IDs (BSUIDs). RelayAPI derives tenant-separated conversation keys, encrypts the raw BSUID at rest, and resolves it only at the provider boundary for a reply in the same authorized conversation. Callers do not supply or retrieve the stored raw BSUID.

Template library and editing

const library = await client.whatsapp.admin.templateLibrary({
  account_id: 'acc_whatsapp123',
  category: 'UTILITY',
  language: 'en_US',
});

await client.whatsapp.admin.createTemplateFromLibrary({
  account_id: 'acc_whatsapp123',
  idempotency_key: 'create-order-update-template',
  name: 'order_update',
  language: 'en_US',
  category: 'UTILITY',
  library_template_name: 'order_update_1',
});

editTemplate() updates only Meta's documented editable fields: components, category, parameter format, message-send TTL, and CTA link-tracking opt-out. Template review/approval remains authoritative at Meta.

Durable mutation behavior

Every WhatsApp admin write requires Idempotency-Key (idempotency_key in the TypeScript SDK) and returns a durable operation. Reuse the same key only for the same logical write. An ambiguous provider outcome is parked as unknown and is not replayed automatically. Poll it with client.whatsapp.admin.getOperation() before deciding whether any manual provider reconciliation is needed. See Published Edits and Social Actions for the shared status contract.

An HTTP 2xx alone is not enough to complete an administration operation. RelayAPI requires the operation-specific success flag or durable group, request, template, or message ID. An unrecognized successful response is recorded as WHATSAPP_PROVIDER_RESPONSE_INVALID with an unknown outcome so it cannot be replayed as though Meta had proved no effect.

RelayAPI does not provide WhatsApp calling/telephony, a proprietary sandbox, manual participant addition, or proprietary template/Flow version history. Those are outside this administration contract.

Automations

WhatsApp is a Tier 1 automation platform. Template messages are required for any send outside the 24-hour customer-service window.

Triggers

TypeFires on
whatsapp_messageInbound message (text / media / sticker / reaction etc.)
whatsapp_keywordReserved trigger alias; use whatsapp_message with keyword config today
whatsapp_button_clickReply from an interactive button
whatsapp_list_replyReply from an interactive list
whatsapp_flow_submitUser completed a Flow
whatsapp_reactionMessage reaction
whatsapp_status_updateDelivery / read status change

Send nodes

All nodes hit POST {GRAPH_BASE.facebook}/{phone-number-id}/messages with messaging_product: "whatsapp".

NodeRequired fields
whatsapp_send_texttext, optional preview_url
whatsapp_send_mediaurl, media_type ∈ image|video|audio|document|sticker; optional caption only for image, video, or document. RelayAPI rejects audio captions before provider I/O; sticker captions are unsupported by the provider.
whatsapp_send_templatetemplate_name, language (default en_US), optional components
whatsapp_send_interactivetext + either buttons[] (≤3 reply buttons) or list (sectioned rows)
whatsapp_send_flowflow_id, flow_token, text, optional cta / flow_action
whatsapp_send_locationlatitude, longitude, optional name / address
whatsapp_send_contactscontacts[] (array of WA contact objects)
whatsapp_reactemoji, optional message_id (defaults to inbound message from state)
whatsapp_mark_readoptional message_id

Outside the 24-hour window, only pre-approved template messages can be sent. Use whatsapp_send_template; free-form whatsapp_send_text will be rejected by Meta.

Official WhatsApp references

Found something wrong? Help us improve this page.

On this page