Skip to main content

WhatsApp Business Cloud ExecModule

Overview

WhatsAppBusinessCloudModule connects ValkyrAI workflows to Meta's hosted WhatsApp Business Cloud API through the native map I/O ExecModule ABI. Workflow Studio discovers it as WhatsAppBusinessCloudModule; the Meta system-user access token resolves only from an encrypted, READY IntegrationAccount.

The initial operation set covers identity, outbound customer messaging, read receipts, reactions, and bounded media lifecycle workflows:

  • list_phone_numbers and get_phone_number
  • send_text, send_template, send_media, send_location, and send_contacts
  • mark_message_read and react_to_message
  • get_media_url, upload_media, and guarded delete_media

Identity and media-metadata reads can retry transient Meta failures. Message sends, receipts, reactions, uploads, and deletions run once so an ambiguous timeout cannot duplicate a customer-visible or destructive side effect.

Usage

  1. Create or select a Meta business portfolio, developer app, WhatsApp Business Account, and registered sender phone number.
  2. Grant the app only the required whatsapp_business_messaging and, when identity discovery is used, whatsapp_business_management permissions.
  3. Create a system-user access token and store it in the encrypted apiKey field of an IntegrationAccount.
  4. Store the numeric WhatsApp Business Account ID in IntegrationAccount.accountId and the default sender phone-number ID in IntegrationAccount.username.
  5. Set the account status to READY and bind it through ExecModuleConfig.authConfig.integrationAccount.
  6. Run list_phone_numbers, then get_phone_number, to verify the bound WABA and sender identity before a mutation.
  7. Use an approved template to start a business-initiated conversation; use free-form messages only when Meta's current customer-service-window and policy rules allow them.
  8. Reconcile provider state before repeating any write after an ambiguous connection failure.

Workflow input cannot supply an access token, WABA ID, or arbitrary provider endpoint. Production routing is fixed to graph.facebook.com/v25.0 over HTTPS. A workflow-supplied phoneNumberId must match the bound IntegrationAccount.username when both are present.

Inputs

NameTypeRequirementDefaultDescription and constraints
operationstringRequiredNoneOne of the 12 documented operations.
phoneNumberIdnumeric stringSender operationsIntegrationAccount.usernameMeta phone-number ID, 1–32 digits; cannot override the bound identity.
recipientPhoneNumberstringOutbound message/reaction operationsNoneE.164 number with 7–15 digits; common punctuation is removed.
textstringsend_textNone1–4,096 characters.
previewUrlbooleanOptional send_textfalseRequests a Meta link preview.
templateNamestringsend_templateNoneApproved template name containing letters, digits, or underscores.
templateLanguagestringsend_templateen_USApproved two- or three-letter language code with optional region.
templateComponentsarray or JSON stringOptional templateNone1–20 components, 256 KiB maximum, eight nesting levels.
mediaTypestringsend_mediaNoneimage, audio, video, document, or sticker.
mediaIdstringMedia send/get/deleteNoneOpaque Meta media ID, maximum 1,024 characters.
mediaUrlHTTPS URLAlternative media sendNonePublic URL, maximum 2,048 characters; mutually exclusive with mediaId.
captionstringOptional image/video/documentNoneMaximum 1,024 characters.
fileNamestringOptional document/uploadNoneSimple filename, maximum 240 characters; path separators are rejected.
latitude / longitudenumbersend_locationNoneLatitude −90–90 and longitude −180–180.
locationName / locationAddressstringOptional locationNoneMaximum 1,000 characters each.
contactsarray or JSON stringsend_contactsNone1–10 bounded contact objects; each requires name.formatted_name.
messageIdstringRead receipt/reactionNoneOpaque message ID, maximum 1,024 characters.
reactionEmojistringreact_to_messageNoneAt most eight Unicode code points; empty removes the reaction where Meta permits it.
mediaBytesBase64base64 stringupload_mediaNoneDecodes to 1 byte through 5 MiB.
mimeTypestringupload_mediaNoneImage, audio, video, PDF, or plain-text media type.
limitintegerOptional phone list1001–100 records from one provider page.
confirmDeletebooleandelete_mediafalseMust be true before the DELETE request is sent.

Structured contact and template data reject excessive arrays, deep nesting, large text, and credential-like nested fields. Contact top-level fields are limited to addresses, birthday, emails, name, org, phones, and urls.

Outputs

NameTypeWhen presentDescription
statusstringAlwayssuccess or error.
operationstringAlwaysNormalized operation name.
attemptsintegerAlwaysTotal provider attempts.
httpStatusintegerProvider respondedLast Meta HTTP status.
dataobjectSuccessNormalized provider response.
items / countarray / integerPhone listPhone identities and returned count.
phoneNumberIdstringPhone identity readVerified Meta phone-number ID.
messageIdstringMessage mutationCreated WhatsApp message ID.
waIdstringMessage mutationNormalized recipient WhatsApp ID returned by Meta.
mediaIdstringMedia upload/readUploaded or inspected media ID.
mediaUrlstringget_media_urlShort-lived provider media URL; never persist it as a durable public URL.
mimeType / fileSizestring / integerMedia readProvider media metadata.
nextCursorstringAnother phone page existsOpaque cursor for an explicit later request.
providerSuccessbooleanReceipt/delete responseMeta's explicit success flag.
errorobjectFailureSafe code, redacted message, optional HTTP status, and retryable flag.

Access tokens never enter outputs. Provider error messages pass through ValkyrAI sensitive-data redaction, and the token-bearing Authorization header is never logged.

IntegrationAccount Requirements

SettingRequirement
ProviderMeta developer app connected to a WhatsApp Business Account
apiKeySystem-user or approved OAuth access token in an encrypted SecureField
passwordLegacy encrypted access-token fallback only; prefer apiKey
accountIdNumeric WhatsApp Business Account ID used by list_phone_numbers
usernameNumeric default sender phone-number ID
statusMust be READY
Permissionswhatsapp_business_messaging; add whatsapp_business_management for management/identity reads

Business verification, app review, system-user provisioning, token rotation, WABA ownership, phone registration, payment configuration, quality monitoring, webhook verification, and template approval belong to the Meta/IntegrationAccount lifecycle. Workflow inputs never handle credentials.

Configuration

{
"version": "1.0.0",
"authConfig": {
"authStrategy": 1,
"integrationAccount": "integration-account:whatsapp-customer-operations"
},
"retryPolicy": {
"maxAttempts": 3,
"backoffStrategy": "EXPONENTIAL",
"initialDelayMs": 1000,
"maxDelayMs": 60000,
"jitter": false
},
"executionConfig": {"timeoutMs": 30000},
"payloadConfig": {
"parameters": "{\"operation\":\"list_phone_numbers\",\"limit\":100}"
}
}

The integration-account value is symbolic. Persisted workflows use the generated relationship rather than plaintext tokens or IDs.

Operations

OperationMeta behaviorSide effect
list_phone_numbersReads one bounded WABA phone-number page.Read-only; safe retries.
get_phone_numberReads the bound sender identity and quality/status fields.Read-only; safe retries.
send_textSends one text message with optional link preview.Customer-visible; single attempt.
send_templateSends one approved template and bounded component set.Customer-visible; single attempt.
send_mediaSends existing or HTTPS-hosted image/audio/video/document/sticker media.Customer-visible; single attempt.
send_locationSends one coordinate with optional name/address.Customer-visible; single attempt.
send_contactsSends 1–10 bounded contact cards.Customer-visible; single attempt.
mark_message_readMarks one inbound message read.Mutates conversation state; single attempt.
react_to_messageAdds, changes, or removes a reaction.Customer-visible; single attempt.
get_media_urlResolves temporary media metadata and URL.Read-only; safe retries.
upload_mediaUploads up to 5 MiB multipart media to the bound sender.Creates provider media; single attempt.
delete_mediaDeletes one provider media object after explicit confirmation.Destructive; single attempt.

Errors and Failure Modes

CodeTypical causeRetryableResolution
VALIDATION_ERRORMissing/invalid ID, recipient, message, media, MIME type, nested data, or deletion guard.NoCorrect input; no unsafe provider request was sent.
UNSUPPORTED_OPERATIONUnknown operation.NoSelect a documented operation.
INTEGRATION_ACCOUNT_REQUIREDNo bound account.NoBind a WhatsApp IntegrationAccount.
INTEGRATION_ACCOUNT_NOT_READYAccount is not READY.NoRepair, authorize, or replace the account.
CREDENTIAL_MISSINGBound account has no decryptable token.NoReconnect or rotate the Meta credential.
WHATSAPP_API_100Invalid field or request shape.Usually noCorrect the redacted provider detail and request.
WHATSAPP_API_131026Recipient/message delivery issue.Provider-definedCheck recipient eligibility, policy, and delivery webhook.
WHATSAPP_API_190_*Expired, revoked, or invalid token.NoRotate and update the IntegrationAccount.
WHATSAPP_API_4, 17, 32, 613, 429, or 5xxMeta throttling or transient provider failure.Yes for readsHonor backoff; reconcile writes before any retry.
NETWORK_ERRORTimeout, DNS, TLS, or connectivity failure.Yes for readsVerify connectivity and provider state.
PAYLOAD_TOO_LARGEJSON request exceeded 256 KiB.NoReduce template/contact data.
RESPONSE_TOO_LARGEResponse exceeded 5 MiB.NoNarrow the identity page or media read.

Meta may return an error_subcode; ValkyrAI appends it to the safe error code without exposing fbtrace_id, tokens, or full provider bodies.

Example

Send one approved order update template:

{
"operation": "send_template",
"recipientPhoneNumber": "+1 (415) 555-0123",
"templateName": "order_update",
"templateLanguage": "en_US",
"templateComponents": [
{
"type": "body",
"parameters": [
{"type": "text", "text": "Order 123"},
{"type": "text", "text": "shipped"}
]
}
]
}

Expected result:

{
"status": "success",
"operation": "send_template",
"attempts": 1,
"httpStatus": 200,
"messageId": "wamid.example",
"waId": "14155550123"
}

Notes

  • Pagination: list_phone_numbers returns one page of at most 100 identities and exposes nextCursor; it does not auto-follow Meta's paging.next URL. This keeps each workflow request bounded and prevents token-bearing provider URLs from becoming workflow input.
  • Rate limits: only list_phone_numbers, get_phone_number, and get_media_url retry HTTP 408, 429, 500, 502, 503, and 504 plus transient Graph codes 1, 2, 4, 17, 32, and 613. Retry-After is honored. All writes remain single-attempt.
  • API limits: JSON bodies are capped at 256 KiB, responses at 5 MiB, upload bytes at 5 MiB, text at 4,096 characters, captions at 1,024, filenames at 240, contacts at 10, component arrays at 20, generic nested arrays at 100, and nesting at eight levels. Provider-specific MIME and size limits may be lower or vary by media type.
  • Idempotency: WhatsApp message, read-receipt, reaction, upload, and deletion endpoints do not expose a universal caller idempotency key. Each write is sent once. Reconcile with delivery/status webhooks, the returned wamid, media IDs, or WhatsApp Manager before retrying after an ambiguous failure.
  • Destructive behavior: only delete_media is destructive and requires confirmDelete=true. Deleting provider media does not retract messages that already reference it.
  • Customer consent and policy: the connector does not infer opt-in, permitted purpose, conversation category, customer-service window, template approval, quality tier, or regional/legal requirements. Workflows must provide approved content and honor Meta policy, applicable law, and organizational consent records.
  • Security: fixed Graph API routing, bound WABA/sender identities, encrypted token storage, E.164 normalization, HTTPS-only media links, strict MIME/filename checks, bounded structured data, forbidden credential-like nested keys, capped bodies/responses, and redacted Graph errors prevent arbitrary routing and secret reflection.
  • Media URLs: get_media_url returns a short-lived Meta URL that still requires authorization. Download bytes promptly through an authorized, separately governed file flow; do not publish or persist the URL as public media.
  • External verification: request construction, response normalization, read retry, single-attempt writes, identity binding, deletion guards, media bounds, redaction, and metadata discovery have deterministic local tests. Live Meta business verification, token permissions, phone registration, template approval, customer consent, delivery, quality/rate limits, and webhook receipts require separately authorized WhatsApp credentials and are not exercised in repository tests.
  • Deferred operations: inbound webhooks/triggers, template CRUD/approval, flows, catalogs/products, interactive list/button messages, groups, calls, payments, business profiles, phone registration, encryption setup, webhook subscription, media-byte download, resumable uploads above 5 MiB, and automated delivery-status reconciliation.
  • Functional references: n8n WhatsApp node source, n8n WhatsApp Business Cloud node, and Meta's official WhatsApp Cloud API collection.