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
SchedulingYes
AnalyticsNo (use WhatsApp Business Manager)

SDK sourceTypeScript · Python · 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. Media captions are limited to 1,024 characters.

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
from relay import Relay

client = Relay()

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

print(post.id)  # post_abc123
client := relaygo.NewClient()

post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Hello from RelayAPI!"),
    Targets:     relaygo.F([]string{"whatsapp"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "whatsapp": map[string]interface{}{
            "to": "14155238886",
        },
    }),
})
RelayClient client = RelayOkHttpClient.fromEnv();

PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Hello from RelayAPI!")
    .addTarget("whatsapp")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("whatsapp", JsonValue.from(Map.of(
            "to", "14155238886"
        )))
        .build())
    .build());

System.out.println(post.id()); // post_abc123
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Hello from RelayAPI!",
    "targets": ["whatsapp"],
    "scheduled_at": "now",
    "target_options": {
      "whatsapp": {
        "to": "14155238886"
      }
    }
  }'

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'
    }
  }
});
post = client.posts.create(
    content='Hello from RelayAPI!',
    targets=['whatsapp'],
    scheduled_at='now',
    target_options={
        'whatsapp': {
            'to': '14155238886'
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Hello from RelayAPI!"),
    Targets:     relaygo.F([]string{"whatsapp"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "whatsapp": map[string]interface{}{
            "to": "14155238886",
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Hello from RelayAPI!")
    .addTarget("whatsapp")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("whatsapp", JsonValue.from(Map.of(
            "to", "14155238886"
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "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
    }
  }
});
post = 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
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Check out our new feature: https://example.com/launch"),
    Targets:     relaygo.F([]string{"whatsapp"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "whatsapp": map[string]interface{}{
            "to":          "14155238886",
            "preview_url": true,
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Check out our new feature: https://example.com/launch")
    .addTarget("whatsapp")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("whatsapp", JsonValue.from(Map.of(
            "to", "14155238886",
            "preview_url", true
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "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'
    }
  }
});
post = 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'
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Check out this photo!"),
    Targets:     relaygo.F([]string{"whatsapp"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{{
        URL:  relaygo.F("https://cdn.example.com/photo.jpg"),
        Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage),
    }}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "whatsapp": map[string]interface{}{
            "to": "14155238886",
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Check out this photo!")
    .addTarget("whatsapp")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/photo.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("whatsapp", JsonValue.from(Map.of(
            "to", "14155238886"
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "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'
    }
  }
});
post = 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'
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Watch our latest update!"),
    Targets:     relaygo.F([]string{"whatsapp"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{{
        URL:  relaygo.F("https://cdn.example.com/video.mp4"),
        Type: relaygo.F(relaygo.PostNewParamsMediaTypeVideo),
    }}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "whatsapp": map[string]interface{}{
            "to": "14155238886",
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Watch our latest update!")
    .addTarget("whatsapp")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/video.mp4")
        .type(PostCreateParams.Media.Type.VIDEO)
        .build())
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("whatsapp", JsonValue.from(Map.of(
            "to", "14155238886"
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "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'
    }
  }
});
post = 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'
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Here is your invoice."),
    Targets:     relaygo.F([]string{"whatsapp"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{{
        URL:  relaygo.F("https://cdn.example.com/invoice.pdf"),
        Type: relaygo.F(relaygo.PostNewParamsMediaTypeDocument),
    }}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "whatsapp": map[string]interface{}{
            "to": "14155238886",
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Here is your invoice.")
    .addTarget("whatsapp")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/invoice.pdf")
        .type(PostCreateParams.Media.Type.DOCUMENT)
        .build())
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("whatsapp", JsonValue.from(Map.of(
            "to", "14155238886"
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "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' }
          ]
        }
      ]
    }
  }
});
post = 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'}
                    ]
                }
            ]
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Targets:     relaygo.F([]string{"whatsapp"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "whatsapp": map[string]interface{}{
            "to":                "14155238886",
            "template_name":     "order_confirmation",
            "template_language": "en_US",
            "template_components": []map[string]interface{}{
                {
                    "type": "body",
                    "parameters": []map[string]interface{}{
                        {"type": "text", "text": "John"},
                        {"type": "text", "text": "#12345"},
                    },
                },
            },
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .addTarget("whatsapp")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("whatsapp", JsonValue.from(Map.of(
            "to", "14155238886",
            "template_name", "order_confirmation",
            "template_language", "en_US",
            "template_components", List.of(Map.of(
                "type", "body",
                "parameters", List.of(
                    Map.of("type", "text", "text", "John"),
                    Map.of("type", "text", "text", "#12345")
                )
            ))
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "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

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.
  • 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.

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
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.

Found something wrong? Help us improve this page.

On this page

Submit an Issue
Requires a GitHub account.View repo