Skip to main content

Bulk Template Message API Documentation

This API allows you to send template messages to multiple recipients simultaneously through the WhatsApp channel.

Message delivery is processed asynchronously; the API request immediately returns a bulkMessageId, while messages are queued and processed in the background.

POST https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/bulk-message/send.js

Key Features

  • Send bulk template messages to 1–5,000 recipients in a single request
  • Personalized variables for each recipient (HEADER, BODY, BUTTONS, CAROUSEL)
  • Shared defaultPayload for all recipients with recipient-specific payload merging (isMerge)
  • Scheduled delivery (scheduleAt)
  • Specify recipients using username (phone number), userId, or sessionId
  • Automatically assign conversations to a user or workgroup after sending (action)
warning

This API can only send templates that have been approved by Meta.

The template name and language code must exactly match the template registered in Meta Business Manager.

Request Parameters

Top-Level Parameters

ParameterTypeRequiredDescription
channelUsernamestringYesWhatsApp channel number used to send the message (e.g. "908506669933")
connectorBrandstringYesChannel connector brand. Currently supported value: "whatsapp"
titlestringNoCampaign title. Used for display in the dashboard. Default: "-"
scheduleAtstring (ISO 8601)NoScheduled delivery date and time (e.g. "2026-06-10T10:00:00.000Z"). If omitted, messages are sent immediately.
defaultPayloadobjectConditional*Default template payload applied to all recipients. Required if a recipient does not provide its own payload.
recipientsarrayYesRecipient list. Minimum 1, maximum 5,000 recipients. Each recipient must be unique.
actionobjectNoAction applied to the conversation after sending. assign-to-user or assign-to-workgroup.

*If every recipient provides its own payload, defaultPayload is optional.

Recipient Structure

For each recipient, exactly one of username, userId, or sessionId must be provided. Multiple identifiers cannot be used together, and omitting all of them will result in an error.

FieldTypeDescription
usernamestringRecipient's phone number (e.g. "905554443322"). If the user does not exist in the system, it is created automatically.
userIdstringID of an existing user in the system.
sessionIdstringID of an existing conversation session in the system.
payloadobjectRecipient-specific template payload (optional). If isMerge: true, it is merged with defaultPayload.
isMergebooleanOverrides merge behavior for this recipient (optional). If omitted, defaultPayload.isMerge is used.

Action Structure

FieldTypeDescription
action.keystring"assign-to-user" assigns the conversation to a specific agent; "assign-to-workgroup" assigns it to a workgroup.
action.valuestringID of the target user or workgroup.

Template Payload Structure

defaultPayload and recipient-specific payload share the same structure. For template messages, type: "template" must be specified.

{
"type": "template",
"template": {
"templateName": "order_confirmation",
"languageCode": "tr-TR",
"variables": []
},
"isMerge": true
}
FieldTypeRequiredDescription
typestringYesPayload type. For template messages: "template"
template.templateNamestringYes*Template name registered in Meta. Optional in recipient payloads when isMerge: true is used (inherits from default).
template.languageCodestringNoTemplate language code (e.g. "tr", "en", "tr-TR", "en-US"). Must exactly match the language registered in Meta.
template.variablesarrayNoList of variables applied to template components. Detailed below.
isMergebooleanNotrue: recipient payload is deep-merged with defaultPayload. false or omitted: recipient payload completely replaces defaultPayload.
isMerge Behavior
  • If defaultPayload.isMerge: true and the recipient provides its own payload, a deep merge is applied.
  • With deep merge, the recipient may provide only variables; templateName and languageCode are inherited from the default payload.
  • The recipient's isMerge field overrides defaultPayload.isMerge.
  • If a recipient does not provide a payload, defaultPayload is always used.

Variables Structure

Each item in the variables array corresponds to a template component. A component type (type) may appear only once in the array.

typeDescriptionparameterscards
"HEADER"Template header component. Text variable or media URL (image, video, document)Yes
"BODY"Template body text variablesYes
"BUTTONS"Dynamic URL suffix variables for buttonsYes
"CAROUSEL"Card components for carousel templatesYes

Parameter Formats

The parameters field can be provided in three different formats:

// Format 1: Array (index-based) — for placeholders like {{1}}, {{2}}, {{3}}
"parameters": ["value1", "value2", "value3"]

// Format 2: Object (numeric keys) — alternative representation for {{1}}, {{2}}
"parameters": { "1": "value1", "2": "value2" }

// Format 3: Object (named keys) — for placeholders like {{name}}, {{amount}}
"parameters": { "name": "John", "amount": "150 USD" }
Which format should I use?
  • If your Meta template uses placeholders like {{1}}, {{2}}, use either the array format or numeric-key object format.
  • If your template uses named placeholders such as {{firstName}} or {{orderTotal}}, use the named-key object format.
  • When using arrays, order matters: the first element maps to {{1}}, the second to {{2}}, and so on.

HEADER Component

Use the HEADER component when the template header contains media (image, video, document) or dynamic text.

// Media header (image URL)
{
"type": "HEADER",
"parameters": ["https://example.com/product-image.jpg"]
}

// Text header variable
{
"type": "HEADER",
"parameters": ["Special Campaign"]
}

BODY Component

Used for dynamic text variables within the template body.

// Array format — when the template uses {{1}}, {{2}}, {{3}}
{
"type": "BODY",
"parameters": ["John", "ORD-12345", "150.00 USD"]
}

// Named format — when the template uses {{name}}, {{orderAmount}}
{
"type": "BODY",
"parameters": {
"name": "John",
"orderAmount": "150.00 USD"
}
}

BUTTONS Component

Used for buttons with dynamic URL suffixes. If the template contains multiple URL buttons, only buttons with variables are indexed sequentially (buttons without variables or FLOW buttons are ignored).

// Single URL button suffix variable
{
"type": "BUTTONS",
"parameters": ["tracking/ORD-12345"]
}

// Multiple URL button suffix variables (first URL button, second URL button)
{
"type": "BUTTONS",
"parameters": ["tracking/ORD-12345", "cancel/ORD-12345"]
}

For carousel templates, component variables are defined separately for each card.

{
"type": "CAROUSEL",
"cards": [
{
"components": [
{
"type": "HEADER",
"parameters": ["https://example.com/card1.jpg"]
},
{
"type": "BODY",
"parameters": ["Product A", "99.00 USD"]
}
]
},
{
"components": [
{
"type": "HEADER",
"parameters": ["https://example.com/card2.jpg"]
},
{
"type": "BODY",
"parameters": ["Product B", "149.00 USD"]
}
]
}
]
}
warning

The length of the cards array must exactly match the number of cards defined in the template.

Examples

1. Simple Template — No Variables, Same Content for All Recipients

This is the simplest way to send a static template without variables to multiple recipients.

{
"channelUsername": "908506669933",
"connectorBrand": "whatsapp",
"title": "General Announcement",
"defaultPayload": {
"type": "template",
"template": {
"templateName": "hello_world",
"languageCode": "tr-TR"
}
},
"recipients": [{ "username": "905554443301" }, { "username": "905554443302" }, { "username": "905554443303" }]
}
curl --location 'https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/bulk-message/send.js' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--data '{
"channelUsername": "908506669933",
"connectorBrand": "whatsapp",
"title": "General Announcement",
"defaultPayload": {
"type": "template",
"template": {
"templateName": "hello_world",
"languageCode": "tr-TR"
}
},
"recipients": [
{ "username": "905554443301" },
{ "username": "905554443302" },
{ "username": "905554443303" }
]
}'
tip
  • Recipients specified via username are automatically created if they do not already exist in the system
  • All recipients use the same defaultPayload; no recipient-specific data is required
  • When isMerge is not specified, each recipient directly uses the defaultPayload

2. Template with Shared Body Variables

The same template is sent to all recipients using identical variable values. The template contains placeholders such as {{1}} and {{2}}.

{
"channelUsername": "908506669933",
"connectorBrand": "whatsapp",
"title": "Summer Campaign",
"defaultPayload": {
"type": "template",
"template": {
"templateName": "promo_notification",
"languageCode": "tr-TR",
"variables": [
{
"type": "BODY",
"parameters": ["30%", "July 31, 2026"]
}
]
}
},
"recipients": [{ "username": "905554443301" }, { "username": "905554443302" }]
}
tip
  • In this example, the template body might contain content such as: "Enjoy a {{1}} discount in our campaign! Offer valid until {{2}}."
  • parameters[0] replaces {{1}}, while parameters[1] replaces {{2}}

3. Personalized Template — Per-Recipient Variables with isMerge

Different variable values are used for each recipient. By setting isMerge: true in the defaultPayload, recipients only need to provide variables; templateName and languageCode are inherited from the default payload.

In this example, the template includes both a media header and personalized body variables.

{
"channelUsername": "908506669933",
"connectorBrand": "whatsapp",
"title": "Personal Order Notification",
"defaultPayload": {
"type": "template",
"template": {
"templateName": "order_confirmation",
"languageCode": "tr-TR"
},
"isMerge": true
},
"recipients": [
{
"username": "905554443301",
"payload": {
"template": {
"variables": [
{
"type": "HEADER",
"parameters": ["https://s3.example.com/product-a.jpg"]
},
{
"type": "BODY",
"parameters": ["Ahmet", "ORD-001", "150.00 TRY"]
}
]
}
}
},
{
"username": "905554443302",
"payload": {
"template": {
"variables": [
{
"type": "HEADER",
"parameters": ["https://s3.example.com/product-b.jpg"]
},
{
"type": "BODY",
"parameters": ["Fatma", "ORD-002", "289.00 TRY"]
}
]
}
}
}
]
}
tip
  • Thanks to isMerge: true, templateName does not need to be repeated in each recipient payload
  • Each recipient's variables array is completely independent
  • The header media URL must be publicly accessible
  • If type is omitted in the recipient payload, defaultPayload.type is inherited

4. URL Button Variable

Use the BUTTONS component when the template contains a button with a dynamic URL suffix. For example, a tracking number appended to the base URL https://example.com/track/.

{
"channelUsername": "908506669933",
"connectorBrand": "whatsapp",
"title": "Shipment Tracking Notification",
"defaultPayload": {
"type": "template",
"template": {
"templateName": "shipment_tracking",
"languageCode": "tr-TR"
},
"isMerge": true
},
"recipients": [
{
"username": "905554443301",
"payload": {
"template": {
"variables": [
{
"type": "BODY",
"parameters": ["Ahmet", "TK-98765"]
},
{
"type": "BUTTONS",
"parameters": ["TK-98765"]
}
]
}
}
},
{
"username": "905554443302",
"payload": {
"template": {
"variables": [
{
"type": "BODY",
"parameters": ["Recep", "TK-12345"]
},
{
"type": "BUTTONS",
"parameters": ["TK-12345"]
}
]
}
}
}
]
}
tip
  • BUTTONS parameters are matched according to the order of buttons that contain variables
  • FLOW buttons and buttons without variables are ignored; indexing only applies to dynamic URL buttons
  • If the template button URL is https://example.com/track/{{1}}, then parameters[0] replaces {{1}}

5. Scheduled Delivery

Schedule a message to be sent at a future date and time. scheduleAt must be provided as an ISO 8601 UTC timestamp.

{
"channelUsername": "908506669933",
"connectorBrand": "whatsapp",
"title": "Morning Campaign Message",
"scheduleAt": "2026-06-10T07:00:00.000Z",
"defaultPayload": {
"type": "template",
"template": {
"templateName": "morning_campaign",
"languageCode": "en-US"
},
"isMerge": true
},
"action": {
"key": "assign-to-workgroup",
"value": "WORKGROUP_ID"
},
"recipients": [
{
"username": "905554443301",
"payload": {
"template": {
"variables": [
{
"type": "BODY",
"parameters": { "name": "Ahmet", "discountRate": "25" }
}
]
}
}
},
{
"username": "905554443302",
"payload": {
"template": {
"variables": [
{
"type": "BODY",
"parameters": { "name": "Zeynep", "discountRate": "40" }
}
]
}
}
}
]
}
tip
  • scheduleAt must be provided in the UTC time zone (e.g., for 10:00 AM Turkey time, use 07:00:00.000Z)
  • This example uses the named parameter format; the template should contain placeholders such as {{name}} and {{discountRate}}
  • With action, conversations created by the message delivery are automatically assigned to the specified workgroup

Response

A successful request returns a bulkMessageId, which can be used to track the delivery process:

{
"bulkMessageId": "abc123xyz789"
}

Error Responses

StatusDescription
Channel not foundNo verified channel matching channelUsername and connectorBrand was found
Channel is not verifiedThe matched channel exists but has not yet been verified
invalidRequestMultiple identifiers were provided for a recipient, a duplicate recipient exists in the list, or a required payload is missing
channelNotFoundNo active channel matching the provided channelId was found
schema-errorA field in the request body failed schema validation (missing required field, incorrect type, or unsupported value)

The returned bulkMessageId can be used with the endpoints below to query delivery statistics and stop the delivery process.


Stats — Delivery Statistics

Retrieves the current delivery status of a bulk message. Returns counts for pending, successful, failed, and cancelled records.

POST https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/bulk-message/stats.js

Request Parameters

ParameterTypeRequiredDescription
bulkMessageIdstringYesID of the bulk message to track (returned from createBulkPerRecipient)
{
"bulkMessageId": "abc123xyz789"
}
curl --location 'https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/bulk-message/stats.js' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--data '{
"bulkMessageId": "abc123xyz789"
}'

Response

{
"bulkMessage": {
"_id": "abc123xyz789",
"slug": "my-workspace",
"title": "Campaign Title",
"channelUsername": "908506669933",
"connectorBrand": "whatsapp",
"connectorSlug": "whatsapp-cloud",
"payload": {
"recipientCount": 100,
"defaultPayloadType": "template",
"jobId": "job_abc123"
},
"schedule": {
"date": "2026-06-10T07:00:00.000Z",
"type": "later"
},
"createdAt": "2026-06-08T10:00:00.000Z",
"updatedAt": "2026-06-08T10:30:00.000Z"
},
"stats": {
"pending": 45,
"success": 42,
"failed": 3,
"cancelled": 10,
"total": 100
}
}

Stats Fields

FieldTypeDescription
stats.pendingnumberNumber of records still waiting in the queue
stats.successnumberNumber of records successfully processed for delivery
stats.failednumberNumber of records that encountered a delivery error
stats.cancellednumberNumber of cancelled records
stats.totalnumberTotal number of records (pending + success + failed + cancelled)
note

stats.success indicates that the message was successfully handed off by the system. It does not guarantee that Meta delivered the message to the recipient. Subsequent status updates generated by Meta (such as delivered, read, or failed) do not affect this counter and are not reflected here.


Stop — Cancel Delivery

Stops an ongoing bulk message delivery. The queued job is cancelled, and all pending records are moved to the cancelled status. Records that have already been processed as success or failed are not affected.

POST https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/bulk-message/stop.js
warning

This action cannot be undone. A stopped delivery cannot be resumed.

Request Parameters

ParameterTypeRequiredDescription
bulkMessageIdstringYesID of the bulk message to stop
{
"bulkMessageId": "abc123xyz789"
}
curl --location 'https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/bulk-message/stop.js' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--data '{
"bulkMessageId": "abc123xyz789"
}'

Response

For a successful request, the response body is empty. An HTTP 200 status indicates that the operation completed successfully.

{}
tip
  • Only bulk messages created through this API can be stopped
  • After stopping a delivery, you can use the stats endpoint to verify the number of cancelled records
  • If the bulk message cannot be found, the API returns the error: bulkMessageNotFound