# Table of Contents - [Dualhook Documentation for WhatsApp Coexistence & Cloud API | Dualhook](#dualhook-documentation-for-whatsapp-coexistence-cloud-api-dualhook) - [About Dualhook | Dualhook](#about-dualhook-dualhook) - [Dualhook Quick Start: Connect Your First WhatsApp Business Account | Dualhook](#dualhook-quick-start-connect-your-first-whatsapp-business-account-dualhook) - [Embedded Signup: featureType, sessionInfoVersion and Coexistence Onboarding | Dualhook](#embedded-signup-featuretype-sessioninfoversion-and-coexistence-onboarding-dualhook) - [WhatsApp Embedded Signup: Implementation Guide | Dualhook](#whatsapp-embedded-signup-implementation-guide-dualhook) - [WhatsApp Connection Not Appearing After Embedded Signup | Dualhook](#whatsapp-connection-not-appearing-after-embedded-signup-dualhook) - [Pricing | Dualhook](#pricing-dualhook) - [WhatsApp Webhook Override: Route Meta Webhooks Directly to Your Server | Dualhook](#whatsapp-webhook-override-route-meta-webhooks-directly-to-your-server-dualhook) - [Billing Enforcement | Dualhook](#billing-enforcement-dualhook) - [WhatsApp Pricing Changes 2026: Aug 1 & Oct 1 | Dualhook](#whatsapp-pricing-changes-2026-aug-1-oct-1-dualhook) - [Support, Contact, and Bug Reports | Dualhook](#support-contact-and-bug-reports-dualhook) - [Troubleshooting | Dualhook](#troubleshooting-dualhook) - [Dualhook Support FAQ: Setup, Webhook Routing, Billing and Bug Reports | Dualhook](#dualhook-support-faq-setup-webhook-routing-billing-and-bug-reports-dualhook) - [Meta Business Portfolio | Dualhook](#meta-business-portfolio-dualhook) - [Meta Business ID | Dualhook](#meta-business-id-dualhook) - [Business Profiles | Dualhook](#business-profiles-dualhook) - [Two-Step Verification (2FA) | Dualhook](#two-step-verification-2fa-dualhook) - [Meta Business Verification for WhatsApp: PLBV vs Standard vs Meta Verified | Dualhook](#meta-business-verification-for-whatsapp-plbv-vs-standard-vs-meta-verified-dualhook) - [Standard Business Verification | Dualhook](#standard-business-verification-dualhook) - [Meta Verified for Business | Dualhook](#meta-verified-for-business-dualhook) - [Official Business Account (OBA) | Dualhook](#official-business-account-oba-dualhook) - [WhatsApp Business Phone Numbers: Add, Register, Limits, Remove | Dualhook](#whatsapp-business-phone-numbers-add-register-limits-remove-dualhook) - [Display Names | Dualhook](#display-names-dualhook) - [WhatsApp Business Accounts | Dualhook](#whatsapp-business-accounts-dualhook) - [Leave a WhatsApp BSP & Own Your WABA (2026) | Dualhook](#leave-a-whatsapp-bsp-own-your-waba-2026-dualhook) - [Runtime API | Dualhook](#runtime-api-dualhook) - [Dualhook Runtime API Errors: Symptoms, Causes and Fixes | Dualhook](#dualhook-runtime-api-errors-symptoms-causes-and-fixes-dualhook) - [Runtime API Endpoint Reference | Dualhook](#runtime-api-endpoint-reference-dualhook) - [WhatsApp Coexistence 13-Day Heartbeat Safety Margin | Dualhook](#whatsapp-coexistence-13-day-heartbeat-safety-margin-dualhook) - [WhatsApp Cloud API Messaging: /messages Endpoint, Statuses and Errors | Dualhook](#whatsapp-cloud-api-messaging-messages-endpoint-statuses-and-errors-dualhook) - [Template Elements | Dualhook](#template-elements-dualhook) - [WhatsApp account_review_update Webhook: WABA Review Decisions | Dualhook](#whatsapp-account-review-update-webhook-waba-review-decisions-dualhook) - [WhatsApp business_capability_update Webhook: Limits and Capabilities | Dualhook](#whatsapp-business-capability-update-webhook-limits-and-capabilities-dualhook) - [Configure Ice Breakers & Commands | Dualhook](#configure-ice-breakers-commands-dualhook) - [Template Analytics | Dualhook](#template-analytics-dualhook) - [WhatsApp Coexistence | Dualhook](#whatsapp-coexistence-dualhook) - [Dualhook API Basics: Auth, Send Payloads, and Webhook Setup | Dualhook](#dualhook-api-basics-auth-send-payloads-and-webhook-setup-dualhook) - [WhatsApp message_template_components_update Webhook Reference | Dualhook](#whatsapp-message-template-components-update-webhook-reference-dualhook) - [WhatsApp security Webhook: Two-Step Verification PIN Events | Dualhook](#whatsapp-security-webhook-two-step-verification-pin-events-dualhook) - [WhatsApp message_template_quality_update Webhook: Template Quality | Dualhook](#whatsapp-message-template-quality-update-webhook-template-quality-dualhook) - [WhatsApp template_category_update Webhook: Template Recategorization | Dualhook](#whatsapp-template-category-update-webhook-template-recategorization-dualhook) - [WhatsApp Error 190: Access Token Expired Fix | Dualhook](#whatsapp-error-190-access-token-expired-fix-dualhook) - [WhatsApp Error 131000: Something Went Wrong | Dualhook](#whatsapp-error-131000-something-went-wrong-dualhook) - [WhatsApp Error 131048: Spam Rate Restriction | Dualhook](#whatsapp-error-131048-spam-rate-restriction-dualhook) - [WhatsApp Error 130429: Throughput Limit Hit | Dualhook](#whatsapp-error-130429-throughput-limit-hit-dualhook) - [WhatsApp Error 131064: Template Classification Limit | Dualhook](#whatsapp-error-131064-template-classification-limit-dualhook) - [WhatsApp Error 131050: Marketing Opt-Out | Dualhook](#whatsapp-error-131050-marketing-opt-out-dualhook) - [WhatsApp Error 80007: WABA Rate Limit Reached | Dualhook](#whatsapp-error-80007-waba-rate-limit-reached-dualhook) - [WhatsApp Ice Breakers & Commands | Dualhook](#whatsapp-ice-breakers-commands-dualhook) - [WhatsApp Error 132001: Template Not Found Fix | Dualhook](#whatsapp-error-132001-template-not-found-fix-dualhook) - [WhatsApp account_alerts Webhook: Alerts, Payloads, Dualhook Handling | Dualhook](#whatsapp-account-alerts-webhook-alerts-payloads-dualhook-handling-dualhook) - [Templates | Dualhook](#templates-dualhook) - [Sending Template Messages | Dualhook](#sending-template-messages-dualhook) - [Marketing Deep Links | Dualhook](#marketing-deep-links-dualhook) - [WhatsApp smb_app_state_sync Webhook: Business App Contact Sync | Dualhook](#whatsapp-smb-app-state-sync-webhook-business-app-contact-sync-dualhook) - [Upload, Retrieve & Delete Media | Dualhook](#upload-retrieve-delete-media-dualhook) - [WhatsApp Error 2655093: Already Shared With a Partner | Dualhook](#whatsapp-error-2655093-already-shared-with-a-partner-dualhook) - [WhatsApp message_template_status_update Webhook Reference | Dualhook](#whatsapp-message-template-status-update-webhook-reference-dualhook) - [WABA Integration Flow: Token Validation, Discovery and Webhook Subscription | Dualhook](#waba-integration-flow-token-validation-discovery-and-webhook-subscription-dualhook) - [WhatsApp Error 131049: Causes & How to Fix It | Dualhook](#whatsapp-error-131049-causes-how-to-fix-it-dualhook) - [WhatsApp Error 130497: Country Restriction Fix | Dualhook](#whatsapp-error-130497-country-restriction-fix-dualhook) - [WhatsApp Error 131026: Message Undeliverable Fix | Dualhook](#whatsapp-error-131026-message-undeliverable-fix-dualhook) - [WhatsApp Webhook Error 100: Subscribe Before Override | Dualhook](#whatsapp-webhook-error-100-subscribe-before-override-dualhook) - [WhatsApp Error 131008: Fix Missing Required Parameter | Dualhook](#whatsapp-error-131008-fix-missing-required-parameter-dualhook) - [WhatsApp Error 131056: 1 Message Every 6 Seconds Pair Rate Limit | Dualhook](#whatsapp-error-131056-1-message-every-6-seconds-pair-rate-limit-dualhook) - [WhatsApp Error 4: Fix App API Call Rate Limit | Dualhook](#whatsapp-error-4-fix-app-api-call-rate-limit-dualhook) - [WhatsApp Error 131042: Fix Payment/Eligibility Issue | Dualhook](#whatsapp-error-131042-fix-payment-eligibility-issue-dualhook) - [WhatsApp Error 133016: Fix Too Many Register Attempts | Dualhook](#whatsapp-error-133016-fix-too-many-register-attempts-dualhook) - [Calling Pricing | Dualhook](#calling-pricing-dualhook) - [WhatsApp Error 130403: Business Blocked the End User | Dualhook](#whatsapp-error-130403-business-blocked-the-end-user-dualhook) - [Compliance & Data Retention | Dualhook](#compliance-data-retention-dualhook) - [MM API Onboarding | Dualhook](#mm-api-onboarding-dualhook) - [WhatsApp Marketing Messages: Cloud API vs Marketing Messages API | Dualhook](#whatsapp-marketing-messages-cloud-api-vs-marketing-messages-api-dualhook) - [WhatsApp account_update Webhook: WABA Lifecycle Events Reference | Dualhook](#whatsapp-account-update-webhook-waba-lifecycle-events-reference-dualhook) - [Location Templates | Dualhook](#location-templates-dualhook) - [WhatsApp history Webhook: Coexistence Chat History Sync | Dualhook](#whatsapp-history-webhook-coexistence-chat-history-sync-dualhook) - [WhatsApp Parent BSUIDs for Multi-Portfolio Businesses | Dualhook](#whatsapp-parent-bsuids-for-multi-portfolio-businesses-dualhook) - [smb_message_echoes Webhook Explained: Coexistence App Messages, Edits, Revokes | Dualhook](#smb-message-echoes-webhook-explained-coexistence-app-messages-edits-revokes-dualhook) - [Reaction Messages | Dualhook](#reaction-messages-dualhook) - [WhatsApp Error 131051: Unsupported Message Type Fixed | Dualhook](#whatsapp-error-131051-unsupported-message-type-fixed-dualhook) - [WhatsApp Business API Permissions | Dualhook](#whatsapp-business-api-permissions-dualhook) - [WhatsApp Utility Templates: Components, Pricing, and Specialty Types | Dualhook](#whatsapp-utility-templates-components-pricing-and-specialty-types-dualhook) - [Messaging Webhook | Dualhook](#messaging-webhook-dualhook) - [WhatsApp Authentication Best Practices: Security & UX | Dualhook](#whatsapp-authentication-best-practices-security-ux-dualhook) - [Media Messages | Dualhook](#media-messages-dualhook) - [WhatsApp Business Platform Webhook Fields Reference | Dualhook](#whatsapp-business-platform-webhook-fields-reference-dualhook) - [WhatsApp QR Codes & Short Links API | Dualhook](#whatsapp-qr-codes-short-links-api-dualhook) - [Contacts & Location Messages | Dualhook](#contacts-location-messages-dualhook) - [WhatsApp Error 100: Invalid Parameter Fix | Dualhook](#whatsapp-error-100-invalid-parameter-fix-dualhook) - [WhatsApp BSUID Contact Book and REQUEST_CONTACT_INFO | Dualhook](#whatsapp-bsuid-contact-book-and-request-contact-info-dualhook) - [WhatsApp Marketing Conversion Tracking: Pixel, CAPI, SDK, fbclid | Dualhook](#whatsapp-marketing-conversion-tracking-pixel-capi-sdk-fbclid-dualhook) - [WhatsApp Management Webhooks: Template, Quality and Account Events | Dualhook](#whatsapp-management-webhooks-template-quality-and-account-events-dualhook) - [Authentication Bulk Management | Dualhook](#authentication-bulk-management-dualhook) - [Marketing Messages API for WhatsApp: Endpoint, Features, Webhooks | Dualhook](#marketing-messages-api-for-whatsapp-endpoint-features-webhooks-dualhook) - [WhatsApp Business Usernames: API, Rules, Webhook and Errors | Dualhook](#whatsapp-business-usernames-api-rules-webhook-and-errors-dualhook) - [Dualhook + Make: WhatsApp Webhook Integration and Runtime Sends | Dualhook](#dualhook-make-whatsapp-webhook-integration-and-runtime-sends-dualhook) - [WhatsApp BSUID, Usernames and Contact Book Transition Guide | Dualhook](#whatsapp-bsuid-usernames-and-contact-book-transition-guide-dualhook) - [Marketing Max-Price (Beta) | Dualhook](#marketing-max-price-beta-dualhook) - [WhatsApp BSUID Webhook Changes: user_id, wa_id, username and parent_user_id | Dualhook](#whatsapp-bsuid-webhook-changes-user-id-wa-id-username-and-parent-user-id-dualhook) - [WhatsApp Calling API Reference: Endpoints, Webhooks, WebRTC | Dualhook](#whatsapp-calling-api-reference-endpoints-webhooks-webrtc-dualhook) - [Marketing Creative Optimizations | Dualhook](#marketing-creative-optimizations-dualhook) - [Messaging Health Status | Dualhook](#messaging-health-status-dualhook) - [WhatsApp OTP Android SDK: Handshake, Eligibility, Migration | Dualhook](#whatsapp-otp-android-sdk-handshake-eligibility-migration-dualhook) - [WhatsApp Embedded Signup Error Codes and Fixes | Dualhook](#whatsapp-embedded-signup-error-codes-and-fixes-dualhook) - [WhatsApp Service Messages: CSW, Types, TTL, and Pricing | Dualhook](#whatsapp-service-messages-csw-types-ttl-and-pricing-dualhook) - [Address Messages (India) | Dualhook](#address-messages-india-dualhook) - [Capacity, Quality & Limits | Dualhook](#capacity-quality-limits-dualhook) - [Dualhook + Zapier: Gateway Pattern for WhatsApp Webhooks and Template Sends | Dualhook](#dualhook-zapier-gateway-pattern-for-whatsapp-webhooks-and-template-sends-dualhook) - [WhatsApp Messaging Best Practices: Opt-In, Quality, Webhooks, Launch Checklist | Dualhook](#whatsapp-messaging-best-practices-opt-in-quality-webhooks-launch-checklist-dualhook) - [WhatsApp API Architecture & Security | Dualhook](#whatsapp-api-architecture-security-dualhook) - [Chatwoot and Dualhook: Safe WhatsApp API Upgrade Guide | Dualhook](#chatwoot-and-dualhook-safe-whatsapp-api-upgrade-guide-dualhook) - [Laravel WhatsApp Webhook Integration with Dualhook | Dualhook](#laravel-whatsapp-webhook-integration-with-dualhook-dualhook) - [WhatsApp Calling SIP Configuration: TLS, Digest Auth, SDES vs DTLS | Dualhook](#whatsapp-calling-sip-configuration-tls-digest-auth-sdes-vs-dtls-dualhook) - [Authentication Messages | Dualhook](#authentication-messages-dualhook) - [WhatsApp Business-Initiated Calls: API and Webhooks | Dualhook](#whatsapp-business-initiated-calls-api-and-webhooks-dualhook) - [WhatsApp BSUID API Changes: recipient, to precedence, groups and calling | Dualhook](#whatsapp-bsuid-api-changes-recipient-to-precedence-groups-and-calling-dualhook) - [WhatsApp Interactive Messages: CTA, List, Carousel, Buttons, Location | Dualhook](#whatsapp-interactive-messages-cta-list-carousel-buttons-location-dualhook) - [WhatsApp Error 132000: Template Variable Mismatch | Dualhook](#whatsapp-error-132000-template-variable-mismatch-dualhook) - [Shopify WhatsApp Integration with Dualhook | Dualhook](#shopify-whatsapp-integration-with-dualhook-dualhook) - [WhatsApp Error 10: Fix Permission Denied / Terms | Dualhook](#whatsapp-error-10-fix-permission-denied-terms-dualhook) - [WhatsApp Error 131047: Re-Engagement / 24h Window Fix | Dualhook](#whatsapp-error-131047-re-engagement-24h-window-fix-dualhook) - [WhatsApp Error 131031: Account Locked Fix | Dualhook](#whatsapp-error-131031-account-locked-fix-dualhook) - [WhatsApp Error 132012: Fix Template Format Mismatch | Dualhook](#whatsapp-error-132012-fix-template-format-mismatch-dualhook) - [WhatsApp Error 368: Fix Temporary Policy Block | Dualhook](#whatsapp-error-368-fix-temporary-policy-block-dualhook) - [WhatsApp Error 200: Fix Missing Token / Permission | Dualhook](#whatsapp-error-200-fix-missing-token-permission-dualhook) - [Timezone IDs | Dualhook](#timezone-ids-dualhook) - [Maintain Account Health | Dualhook](#maintain-account-health-dualhook) - [WhatsApp Cloud API Calling: VoIP, BIC/UIC, Architecture | Dualhook](#whatsapp-cloud-api-calling-voip-bic-uic-architecture-dualhook) - [WhatsApp Error 131009: Parameter Value Not Valid Fix | Dualhook](#whatsapp-error-131009-parameter-value-not-valid-fix-dualhook) - [Dualhook + n8n: WhatsApp Webhook Receiver, Meta Verification, and Template Sends | Dualhook](#dualhook-n8n-whatsapp-webhook-receiver-meta-verification-and-template-sends-dualhook) - [Node.js WhatsApp Webhook Receiver with Dualhook | Dualhook](#node-js-whatsapp-webhook-receiver-with-dualhook-dualhook) - [Call Buttons & Deep Links | Dualhook](#call-buttons-deep-links-dualhook) - [WhatsApp User-Initiated Calls: Inbound Voice with Pre-Accept | Dualhook](#whatsapp-user-initiated-calls-inbound-voice-with-pre-accept-dualhook) - [WhatsApp Calling Troubleshooting: Error Codes & Audio Clipping | Dualhook](#whatsapp-calling-troubleshooting-error-codes-audio-clipping-dualhook) - [Calling Configuration | Dualhook](#calling-configuration-dualhook) - [WhatsApp Call Permissions: Temporary, Permanent, and Callback | Dualhook](#whatsapp-call-permissions-temporary-permanent-and-callback-dualhook) - [WhatsApp Cloud API Error Codes — Full Meta API Reference & Fixes | Dualhook](#whatsapp-cloud-api-error-codes-full-meta-api-reference-fixes-dualhook) --- # Dualhook Documentation for WhatsApp Coexistence & Cloud API | Dualhook Browse Docs Documentation ============= Learn how Dualhook works, from initial setup to advanced configuration and WhatsApp platform operations. Getting Started --------------- [### Quick Start\ \ Set up Dualhook and connect your first WhatsApp Business Account in minutes.](https://dualhook.com/docs/getting-started) [### About Dualhook\ \ What Dualhook is, where it fits in the WhatsApp ecosystem, and how it works.](https://dualhook.com/docs/about) [### Embedded Signup\ \ How Dualhook uses Meta's Embedded Signup flow to connect WhatsApp Business Accounts via OAuth.](https://dualhook.com/docs/embedded-signup) [### WhatsApp Embedded Signup Guide\ \ The vendor-neutral implementer's guide to Meta's Embedded Signup flow: prerequisites, the exact client and server calls, Coexistence, v4, and what breaks in production.](https://dualhook.com/docs/whatsapp-embedded-signup-guide) [### Connection Not Appearing After Embedded Signup\ \ Why Meta can finish linking while no Dualhook connection appears, and how to retry the browser handoff safely.](https://dualhook.com/docs/connection-not-appearing) [### WhatsApp Webhook Override\ \ How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server.](https://dualhook.com/docs/webhook-override) [### Pricing\ \ Dualhook subscription plans, what is included, and how pricing relates to Meta messaging fees.](https://dualhook.com/docs/pricing) [### WhatsApp Pricing Changes 2026\ \ Meta's 2026 WhatsApp pricing changes: Meta Business Agent billing, 1,000 free monthly service messages per number, chargeable service and in-window utility messages, and October rate cards.](https://dualhook.com/docs/whatsapp-pricing-changes-2026) [### Billing Enforcement\ \ What happens to Dualhook connections when a subscription ends or a plan limit is exceeded.](https://dualhook.com/docs/billing-enforcement) [### Troubleshooting\ \ Common issues and solutions for Dualhook connections, webhooks, and health monitoring.](https://dualhook.com/docs/troubleshooting) [### Support, Contact, and Bug Reports\ \ How to contact Dualhook support, use the in-app support chat, report bugs, attach screenshots or PDFs, and know when email is the right path.](https://dualhook.com/docs/contact-support) [### Support FAQ\ \ Frequently asked questions about WhatsApp Business Platform and Dualhook.](https://dualhook.com/docs/support-faq) Meta Platform ------------- [### Meta Business Portfolio\ \ The top-level container for business assets in Meta and why it matters for WhatsApp.](https://dualhook.com/docs/meta-business-portfolio) [### Meta Business ID\ \ How to find your Meta Business ID and the difference between Business ID and WABA ID.](https://dualhook.com/docs/meta-business-id) [### WhatsApp Business Accounts\ \ What a WABA is, how it relates to phone numbers, and how ownership works.](https://dualhook.com/docs/whatsapp-business-accounts) [### How to Leave Your WhatsApp BSP and Own Your WABA\ \ Migrate a WhatsApp business phone number away from a Solution Partner: what Meta preserves, what resets, the prerequisites, billing during migration, and the Coexistence caveat.](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba) [### Phone Numbers\ \ Managing phone numbers in WhatsApp Business Platform: adding, removing, and limits.](https://dualhook.com/docs/phone-numbers) [### Display Names\ \ How display names work, naming rules, review statuses, and common rejection patterns.](https://dualhook.com/docs/display-names) [### Business Profiles\ \ Managing the public-facing WhatsApp business profile for your phone number.](https://dualhook.com/docs/business-profiles) [### Official Business Account (OBA)\ \ The WhatsApp blue checkmark: eligibility, notability requirements, and limitations.](https://dualhook.com/docs/official-business-account) [### Two-Step Verification (2FA)\ \ Phone-number-level security using a 6-digit PIN for WhatsApp Business Platform.](https://dualhook.com/docs/two-step-verification) Verification ------------ [### Meta Business Verification\ \ Overview of verification paths: PLBV, Standard, and Meta Verified for Business.](https://dualhook.com/docs/meta-business-verification) [### Standard Business Verification\ \ The standard verification flow through Meta Security Center for non-coexistence accounts.](https://dualhook.com/docs/standard-business-verification) [### Meta Verified for Business\ \ Paid subscription for trust, badge, and brand-protection features across Meta assets.](https://dualhook.com/docs/meta-verified-for-business) Coexistence ----------- [### WhatsApp Coexistence\ \ How Coexistence mode works: using WhatsApp Business App and Cloud API on the same number.](https://dualhook.com/docs/coexistence) [### 13-Day Heartbeat Safety Margin\ \ How Meta's approximate 14-day inactivity threshold relates to Dualhook's 13-day safety timer.](https://dualhook.com/docs/heartbeat) Templates & Messaging --------------------- [### Templates\ \ Template lifecycle, categories, and management through Dualhook and Meta Graph API.](https://dualhook.com/docs/templates) [### Template Elements\ \ Template structure: headers, body, footer, buttons, variables, and practical limits.](https://dualhook.com/docs/template-elements) [### Template Analytics\ \ Measuring template performance: sent, delivered, read, and clicked metrics.](https://dualhook.com/docs/template-analytics) [### Sending Template Messages\ \ How to send template messages via Cloud API with variables, media headers, and URL buttons.](https://dualhook.com/docs/sending-template-messages) [### Marketing Messages\ \ Marketing template messaging through Dualhook's standard Cloud API runtime, plus boundaries for Meta's separate MM API product.](https://dualhook.com/docs/marketing-messages) [### Marketing Messages API for WhatsApp\ \ Reference for Meta's separate /marketing\_messages product, which is not exposed by Dualhook's runtime or Embedded Signup configuration.](https://dualhook.com/docs/marketing-messages-api) [### MM API Onboarding\ \ Reference for MM API onboarding outside Dualhook's managed configuration. Dualhook does not request Marketing Messages Lite.](https://dualhook.com/docs/marketing-messages-onboarding) [### Marketing Conversion Tracking\ \ Measure conversions from marketing messages with Meta Pixel, Conversions API, and the Meta SDK. Includes fbclid handling, the click-event webhook, and Android deep-link attribution.](https://dualhook.com/docs/marketing-conversion-tracking) [### Marketing Creative Optimizations\ \ Configure Meta's automatic creative optimizations on MM API marketing templates: creative\_features\_spec at template and WABA level, opt-in/opt-out per key.](https://dualhook.com/docs/marketing-creative-optimizations) [### Marketing Deep Links\ \ Map a URL button on a marketing template to an Android app deep link via app\_deep\_link, with fallback URLs for non-Android clients and uninstalled-app users.](https://dualhook.com/docs/marketing-deep-links) [### Marketing Max-Price (Beta)\ \ Set a maximum price per marketing message delivery on MM API for WhatsApp using bid\_spec. Includes per-message multiplier, reach estimation, and rollout phases.](https://dualhook.com/docs/marketing-max-price) [### Utility Messages\ \ Utility template messaging hub: components and buttons, specialty types, the October 2026 CSW billing change, volume tiers, content rules, and recategorization.](https://dualhook.com/docs/utility-messages) [### Location Templates\ \ WhatsApp templates with a map header. Specify latitude, longitude, name, and address at send time. Useful for delivery updates, store finders, ride-hailing pickup and drop-off.](https://dualhook.com/docs/utility-templates-location) [### Authentication Messages\ \ OTP and verification code delivery via WhatsApp: copy-code, one-tap, and zero-tap templates, linked device security, the 'I didn't request a code' webhook, and PendingIntent deprecation.](https://dualhook.com/docs/authentication-messages) [### OTP Handshake & Android SDK\ \ How the WhatsApp OTP handshake works for one-tap and zero-tap authentication templates: OTP Android SDK, manifest setup, eligibility checks, multi-app support, error codes, and PendingIntent deprecation.](https://dualhook.com/docs/authentication-otp-handshake) [### Authentication Bulk Management\ \ Use upsert\_message\_templates to create or update authentication templates across many languages in one call, and use message\_template\_previews to preview localized text before committing.](https://dualhook.com/docs/authentication-bulk-management) [### Authentication Best Practices\ \ Security and UX best practices for WhatsApp authentication: identity hash binding for recycled phone protection, opt-in collection, isWhatsAppInstalled checks for Android and iOS, multi-app support.](https://dualhook.com/docs/authentication-best-practices) Webhooks & API -------------- [### The Basics Overview\ \ Base URL, authentication, message send payloads, and webhook subscription basics.](https://dualhook.com/docs/basics-overview) [### Runtime API\ \ Use, protect, rotate, and scope dh\_live\_ keys for Dualhook's allowlisted Graph-compatible WhatsApp runtime.](https://dualhook.com/docs/runtime-api) [### Runtime API Endpoint Reference\ \ Graph-compatible message, media, template, health, profile, phone-settings, and QR endpoints for connection-scoped dh\_live\_ keys.](https://dualhook.com/docs/runtime-api-reference) [### Runtime API Errors\ \ Troubleshoot Dualhook Runtime API failures by symptom: revoked keys, rejected media, wrong-connection scope, unsupported fields, validation, rate limits, and the send failure circuit.](https://dualhook.com/docs/runtime-api-errors) [### Messaging Overview\ \ Core messaging endpoint, text messages, formatting, delivery statuses, and common errors.](https://dualhook.com/docs/messaging-overview) [### Messaging Webhook\ \ Real-time webhook events for inbound messages, delivery statuses, and errors.](https://dualhook.com/docs/messaging-webhook) [### Webhook Events & Notifications\ \ Management webhook events: template status, quality updates, account alerts, and forwarding.](https://dualhook.com/docs/webhook-events) [### Webhook Subscription Fields\ \ Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting.](https://dualhook.com/docs/webhooks) [### account\_alerts Webhook\ \ Field reference for WhatsApp account\_alerts webhooks: messaging-limit alerts, OBA status, profile-photo alerts, payload shape, and Dualhook handling.](https://dualhook.com/docs/webhook-account-alerts) [### account\_review\_update Webhook\ \ Field reference for WhatsApp account\_review\_update webhooks: WABA review decisions, payload shape, and Dualhook handling.](https://dualhook.com/docs/webhook-account-review-update) [### account\_update Webhook\ \ Field reference for WhatsApp account\_update webhooks: WABA lifecycle events, restrictions, partner events, pricing tiers, MM API terms, offboarding, and Dualhook handling.](https://dualhook.com/docs/webhook-account-update) [### business\_capability\_update Webhook\ \ Field reference for WhatsApp business\_capability\_update webhooks: messaging limits, phone-number caps, capability changes, and Dualhook handling.](https://dualhook.com/docs/webhook-business-capability-update) [### history Webhook\ \ Field reference for WhatsApp history webhooks: Coexistence chat-history sync, phases, chunks, declined sharing, direct routing, and Dualhook handling.](https://dualhook.com/docs/webhook-history) [### smb\_app\_state\_sync Webhook\ \ Field reference for WhatsApp smb\_app\_state\_sync webhooks: Coexistence contact sync, add/remove actions, direct routing, and Dualhook handling.](https://dualhook.com/docs/webhook-smb-app-state-sync) [### smb\_message\_echoes Webhook\ \ Field reference for WhatsApp smb\_message\_echoes webhooks: Business App sent-message echoes, edits, revokes, direct routing, and Dualhook handling.](https://dualhook.com/docs/webhook-smb-message-echoes) [### message\_template\_components\_update Webhook\ \ Field reference for WhatsApp message\_template\_components\_update webhooks: template component edits, button fields, payload shape, and Dualhook handling.](https://dualhook.com/docs/webhook-message-template-components-update) [### message\_template\_quality\_update Webhook\ \ Field reference for WhatsApp message\_template\_quality\_update webhooks: template quality score changes, payload shape, and Dualhook handling.](https://dualhook.com/docs/webhook-message-template-quality-update) [### message\_template\_status\_update Webhook\ \ Field reference for WhatsApp message\_template\_status\_update webhooks: template approval, rejection, pause, disablement, archive, deletion, appeal, and Dualhook handling.](https://dualhook.com/docs/webhook-message-template-status-update) [### security Webhook\ \ Field reference for WhatsApp security webhooks: two-step verification PIN changes, reset requests, requester IDs, payload shape, and Dualhook handling.](https://dualhook.com/docs/webhook-security) [### template\_category\_update Webhook\ \ Field reference for WhatsApp template\_category\_update webhooks: scheduled and completed template recategorization, payload shape, and Dualhook handling.](https://dualhook.com/docs/webhook-template-category-update) [### Service Messages\ \ Free-form messages inside an open customer service window: types, replies, TTL, and October 2026 pricing with 1,000 free messages per number each month.](https://dualhook.com/docs/service-messages) [### Interactive Service Messages\ \ Interactive WhatsApp messages: CTA URL button, reply buttons, list, media carousel, and location request. Includes payload shapes and inbound-webhook formats for each type.](https://dualhook.com/docs/service-messages-interactive) [### Address Messages (India)\ \ India-only interactive form for collecting structured shipping addresses inside WhatsApp. Covers prefill, saved addresses, validation errors, the nfm\_reply submission webhook, and feature-not-supported (error 1026) fallback.](https://dualhook.com/docs/service-messages-address) [### Reaction Messages\ \ Apply emoji reactions to WhatsApp user messages. Limitations (sent-only status, 30-day window, error 131009), how to remove a reaction, and inbound user reaction webhooks.](https://dualhook.com/docs/service-messages-reactions) [### Media Messages\ \ Sending and receiving images, video, audio, documents, and stickers via Cloud API.](https://dualhook.com/docs/media-messages) [### Upload, Retrieve & Delete Media\ \ Media lifecycle: upload files, retrieve download URLs, and delete media objects.](https://dualhook.com/docs/media-upload) [### Contacts & Location Messages\ \ Sending and receiving contact cards and location messages via Cloud API.](https://dualhook.com/docs/contacts-location) [### QR Codes & Short Links\ \ Create, list, update, and delete WhatsApp QR codes and short links via the Business Management API. Each code prefills a message in the chat — useful for campaigns, signage, and tenant-specific entry points.](https://dualhook.com/docs/qr-codes) [### Ice Breakers & Commands\ \ Ice breakers and slash commands for WhatsApp business numbers, plus the removal of request\_welcome. (Note: for interactive buttons/lists/carousels, see Interactive Service Messages.)](https://dualhook.com/docs/welcome-ice-breakers-commands) [### Configure Ice Breakers & Commands\ \ Step-by-step setup and testing of ice breakers and commands.](https://dualhook.com/docs/welcome-ice-breakers-commands-config) [### WABA Integration\ \ End-to-end WhatsApp Business Account integration: token validation, discovery, and subscription.](https://dualhook.com/docs/waba-integration) [### Meta API Error Reference\ \ Complete WhatsApp Cloud API and Meta Graph error code reference — what each code means, where it appears, and how to fix it.](https://dualhook.com/docs/api-errors) WhatsApp Error Codes -------------------- [### Embedded Signup Errors\ \ Searchable Meta Embedded Signup error index with safe first actions for business, WABA, phone, OTP, sharing, and account restrictions.](https://dualhook.com/docs/embedded-signup-errors) [### WhatsApp Error 2655093: Account Already Shared With a Partner\ \ How to safely release an old BSP or Coexistence partner relationship when Meta blocks Embedded Signup with error 2655093.](https://dualhook.com/docs/whatsapp-error-2655093) [### WhatsApp Error 131049: Healthy Ecosystem Engagement\ \ WhatsApp error 131049 means Meta withheld a marketing message to maintain healthy ecosystem engagement. Causes, the per-user limit, and how to fix it.](https://dualhook.com/docs/whatsapp-error-131049) [### WhatsApp Error 131047: Re-Engagement Message Required\ \ WhatsApp error 131047 means 24+ hours passed since the customer last replied, so the service window is closed. Why it happens and how to fix it with templates.](https://dualhook.com/docs/whatsapp-error-131047) [### WhatsApp Error 131026: Message Undeliverable\ \ WhatsApp error 131026 means the message is undeliverable — not a WhatsApp number, unaccepted Terms, or an outdated app. Causes and how to fix it.](https://dualhook.com/docs/whatsapp-error-131026) [### WhatsApp/Graph Error 190: Access Token Expired\ \ WhatsApp/Graph error 190 means your access token expired or is invalid. Why it happens and how to switch to a long-lived system-user token.](https://dualhook.com/docs/whatsapp-error-190) [### WhatsApp Error 130497: Country Restriction\ \ WhatsApp error 130497 means your WABA is restricted from messaging users in certain countries. Causes, the scaling path, and Brazil/Indonesia limits.](https://dualhook.com/docs/whatsapp-error-130497) [### WhatsApp Error 132001: Template Not Found\ \ WhatsApp error 132001 means the template doesn't exist in that language/locale or isn't approved. Match the exact name, locale, approval state, and WABA context.](https://dualhook.com/docs/whatsapp-error-132001) [### WhatsApp Error 132000: Template Variable Mismatch\ \ WhatsApp error 132000 means the parameter count in your request doesn't match the approved template. Fix body, header, and button variables to match exactly.](https://dualhook.com/docs/whatsapp-error-132000) [### WhatsApp Error 131056: Pair Rate Limit Hit\ \ WhatsApp error 131056 means too many messages to the same recipient too fast (pair rate limit). How to pace per-recipient sends, back off, and prevent it.](https://dualhook.com/docs/whatsapp-error-131056) [### WhatsApp Error 131031: Account Has Been Locked\ \ WhatsApp error 131031 means your WABA is restricted/disabled for a policy violation, or request data (e.g. two-step PIN) could not be verified. How to recover.](https://dualhook.com/docs/whatsapp-error-131031) [### WhatsApp Error 131050: Recipient Opted Out of Marketing\ \ WhatsApp error 131050 means the recipient opted out of your marketing messages. You cannot override it — stop retrying and track opt-outs via user\_preferences.](https://dualhook.com/docs/whatsapp-error-131050) [### WhatsApp Webhook Error 100: "Before Override the Current Callback URI…"\ \ "(#100) Before override the current callback uri" means your app must subscribe to the WABA messages field first. The two-step fix, plus webhook 2200/2201.](https://dualhook.com/docs/whatsapp-error-100-webhook-override) [### WhatsApp Error 100: Invalid Parameter\ \ WhatsApp error 100 means Meta rejected a value or field in your request. Read error\_data.details, fix the recipient format, message type, or template component, and resend.](https://dualhook.com/docs/whatsapp-error-100) [### WhatsApp Error 130429: Throughput Limit Hit\ \ WhatsApp error 130429 means your send rate exceeded the per-number throughput Meta allows (default ~80 msg/sec). How to queue, shard, and back off safely.](https://dualhook.com/docs/whatsapp-error-130429) [### WhatsApp Error 131048: Spam Rate Restriction\ \ WhatsApp error 131048 means the phone number is restricted because previous messages were blocked or flagged as spam. How to recover number health safely.](https://dualhook.com/docs/whatsapp-error-131048) [### WhatsApp Error 131000: Something Went Wrong\ \ WhatsApp error 131000 is Meta’s generic "something went wrong" send failure. When to retry, and how to investigate persistent unknown errors with fbtrace\_id.](https://dualhook.com/docs/whatsapp-error-131000) [### WhatsApp Error 80007: WABA Rate Limit Reached\ \ WhatsApp error 80007 means the WhatsApp Business Account hit its rate limit on management/Graph calls. How to back off, cache, and stop over-polling.](https://dualhook.com/docs/whatsapp-error-80007) [### WhatsApp Error 132012: Template Parameter Format Mismatch\ \ WhatsApp error 132012 means template variable values are formatted incorrectly (e.g. header expected IMAGE, received UNKNOWN). Causes, fixes, and prevention.](https://dualhook.com/docs/whatsapp-error-132012) [### WhatsApp Error 131042: Business Eligibility / Payment Issue\ \ WhatsApp error 131042 means your account has a payment-method problem blocking sends. The full Meta cause list, how to restore billing eligibility, and prevention.](https://dualhook.com/docs/whatsapp-error-131042) [### WhatsApp Error 131008: Required Parameter Is Missing\ \ WhatsApp error 131008 means the request is missing a required parameter — common with flow buttons and dynamic template buttons. Causes and step-by-step fixes.](https://dualhook.com/docs/whatsapp-error-131008) [### WhatsApp Error 131009: Parameter Value Not Valid\ \ WhatsApp error 131009 means a parameter has the right name but a value Meta cannot use: check field formats, supported values, and object IDs. How to read details and fix it.](https://dualhook.com/docs/whatsapp-error-131009) [### WhatsApp Error 131051: Unsupported Message Type\ \ WhatsApp error 131051 means an unsupported message type — often inbound edited, deleted, poll, or unavailable messages hitting your webhook handler. What to do.](https://dualhook.com/docs/whatsapp-error-131051) [### WhatsApp Error 368: Temporarily Blocked for Policy Violations\ \ WhatsApp error 368 means your WhatsApp Business Account was restricted or disabled for a platform policy violation. How to diagnose, appeal, and prevent it.](https://dualhook.com/docs/whatsapp-error-368) [### WhatsApp Error 4: App Has Reached Its API Call Rate Limit\ \ WhatsApp error 4 means the app has reached its API call rate limit (Graph app-level, not message throughput). How to back off, batch, and prevent it.](https://dualhook.com/docs/whatsapp-error-4) [### WhatsApp Error 200: No Access Token / Permission Issue\ \ WhatsApp error 200 means no access token was provided ('Provide valid app ID'); in practice it also surfaces as a missing-permission-to-send error. How to fix both.](https://dualhook.com/docs/whatsapp-error-200) [### WhatsApp Error 10: Permission Denied\ \ WhatsApp error 10 means permission is not granted or has been removed — missing scopes, unaccepted terms, or (for Flows) a non-allowlisted number. How to fix it.](https://dualhook.com/docs/whatsapp-error-10) [### WhatsApp Error 133016: Too Many Registration Attempts\ \ WhatsApp error 133016 means registration/deregistration failed due to too many attempts for the number in a short period. Why it happens and how to recover.](https://dualhook.com/docs/whatsapp-error-133016) [### WhatsApp Error 130403: Business Has Blocked the End User\ \ WhatsApp error 130403 means your business has blocked the recipient on WhatsApp, so the message can't be delivered. Do not retry — unblock the user instead.](https://dualhook.com/docs/whatsapp-error-130403) [### WhatsApp Error 131064: Messaging Limit from Template Classification Violations\ \ WhatsApp error 131064 means your account hit a messaging limit due to template classification violations, affecting templates and direct sends. How to recover.](https://dualhook.com/docs/whatsapp-error-131064) Calling ------- [### Cloud API Calling\ \ Voice calling on WhatsApp Business Cloud API: VoIP via WebRTC or SIP, business-initiated and user-initiated calls, prerequisites, availability, and integration patterns.](https://dualhook.com/docs/calling) [### Calling Pricing\ \ How Meta bills WhatsApp Calling: free user-initiated calls, per-pulse business-initiated billing, volume tiers, customer service window interaction, call permission request charges, billing prerequisites.](https://dualhook.com/docs/calling-pricing) [### Calling Configuration\ \ Enable calling on a business phone number, configure call hours and holidays, restrict the call icon by country, set callback permissions, choose audio codecs, listen for settings webhooks.](https://dualhook.com/docs/calling-configuration) [### Call Permissions\ \ Obtain permission to call WhatsApp users: temporary (7-day) and permanent permissions, three ways to obtain (free-form request, template request, callback auto-prompt, business profile), get permission state endpoint, permission reply webhook.](https://dualhook.com/docs/calling-permissions) [### SIP Configuration\ \ Use SIP signaling instead of Graph API + webhooks for WhatsApp calling. TLS certificate requirements, digest authentication, business and user initiated SIP flows, SDES vs DTLS for SRTP, custom SIP headers.](https://dualhook.com/docs/calling-sip) [### Business-Initiated Calls\ \ Place outbound voice calls to WhatsApp users. POST /calls with action: connect, SDP offer/answer flow, call status webhooks (RINGING/ACCEPTED/REJECTED), terminate webhook with duration, biz\_opaque\_callback\_data tracking.](https://dualhook.com/docs/calling-business-initiated) [### User-Initiated Calls\ \ Receive inbound voice calls from WhatsApp users. Call Connect webhook with SDP offer, pre-accept for faster setup, accept/reject/terminate actions, DTMF dial pad, companion-device support.](https://dualhook.com/docs/calling-user-initiated) [### Call Buttons & Deep Links\ \ Drive WhatsApp calls from messages and links: voice\_call interactive messages, voice\_call template buttons, wa.me/call/ deep links with biz\_payload tracking.](https://dualhook.com/docs/calling-call-buttons) [### Calling API Reference\ \ Consolidated WhatsApp Calling reference: every endpoint, every webhook, WebRTC and ICE guidelines, DTLS certificate recommendations, SDP requirements, bandwidth budget, IP allowlisting.](https://dualhook.com/docs/calling-api-reference) [### Timezone IDs\ \ Reference for the numeric timezone\_id field returned by Meta APIs. Maps to TZ\_\_ constants. Used by call\_hours.timezone\_id (which takes IANA name on write) and other Meta resources.](https://dualhook.com/docs/calling-timezone-ids) [### Calling Troubleshooting\ \ WhatsApp Calling errors and fixes: full error code table (138000-138023), SIP-specific status codes, media-flow timeouts, audio clipping causes and mitigations (SDES, pre-accept), quick triage guide.](https://dualhook.com/docs/calling-troubleshooting) Operations ---------- [### Messaging Health Status\ \ Monitor phone number health: AVAILABLE, LIMITED, BLOCKED states and quality rating.](https://dualhook.com/docs/health-monitoring) [### Capacity, Quality & Limits\ \ API rate limits, messaging limit tiers, quality rating, and scaling guidance.](https://dualhook.com/docs/capacity-quality-limits) [### Maintain Account Health\ \ Ownership hygiene, quality monitoring, opt-out governance, and incident playbook.](https://dualhook.com/docs/account-health) [### Messaging Best Practices\ \ Opt-in, notification quality, contact validation, callback handling, and pre-launch checklist.](https://dualhook.com/docs/messaging-best-practices) [### Meta BSUID, Usernames & Contact Book Transition Guide\ \ Overview of Meta's WhatsApp BSUID rollout: usernames, contact book behavior, parent BSUIDs, API recipient support, webhook changes, and migration timeline.](https://dualhook.com/docs/contacts-bsuid) [### BSUID Webhook Changes\ \ WhatsApp webhook payload changes for BSUIDs, usernames, parent BSUIDs, optional phone-number fields, statuses, contacts, groups, calling, and Coexistence events.](https://dualhook.com/docs/contacts-bsuid-webhooks) [### BSUID API and Sending Changes\ \ How WhatsApp APIs use BSUIDs and parent BSUIDs in the recipient field for messages, Marketing Messages API, groups, block users, calling, and SIP.](https://dualhook.com/docs/contacts-bsuid-api-changes) [### BSUID Contact Book and Contact Requests\ \ Meta contact book behavior for WhatsApp BSUIDs, phone-number inclusion rules, contact book deletion, REQUEST\_CONTACT\_INFO, and contacts webhooks.](https://dualhook.com/docs/contacts-bsuid-contact-book) [### Parent BSUIDs\ \ Parent BSUID behavior for eligible multi-portfolio WhatsApp businesses, including payload fields, account lookup, supported APIs, and limitations.](https://dualhook.com/docs/contacts-bsuid-parent-user-ids) [### WhatsApp Business Usernames\ \ Business username rules for WhatsApp business phone numbers, including format, display priority, reserved usernames, management APIs, webhook updates, and errors.](https://dualhook.com/docs/whatsapp-business-usernames) [### WhatsApp Business API Permissions\ \ OAuth scopes, WABA task access, delegated Meta access, and troubleshooting missing WhatsApp Business API permissions.](https://dualhook.com/docs/whatsapp-business-api-permissions) [### Compliance & Data Retention\ \ Privacy boundary, configurable retention, CSV export, and organization isolation.](https://dualhook.com/docs/compliance) [### WhatsApp API Architecture & Security\ \ Cloud API architecture, encryption, data residency, GDPR, and Dualhook's direct-routing security model.](https://dualhook.com/docs/architecture-security) Implementation Guides --------------------- [### Chatwoot WhatsApp Integration\ \ Choose the safe Chatwoot migration path for customer-owned and Dualhook Meta apps across Chatwoot Cloud and self-hosted deployments.](https://dualhook.com/docs/guide-chatwoot-webhook) [### n8n Webhook Integration\ \ Use n8n as a direct WhatsApp webhook receiver with Meta verification, payload-shape validation, and outbound sends through Dualhook's runtime.](https://dualhook.com/docs/guide-n8n-webhook) [### Make Webhook Integration\ \ Use Make Custom Webhooks for direct Meta deliveries, payload-shape validation, and WhatsApp template sends.](https://dualhook.com/docs/guide-make-webhook) [### Zapier Webhook Integration\ \ Use a lightweight gateway for Meta verification, then forward approved WhatsApp events into Zapier for downstream automation.](https://dualhook.com/docs/guide-zapier-webhook) [### Node.js Webhook Receiver\ \ Set up a Node.js Express endpoint to receive WhatsApp webhooks routed by Dualhook.](https://dualhook.com/docs/guide-nodejs-webhook) [### Laravel Webhook Integration\ \ Build a Laravel webhook controller for WhatsApp messages routed through Dualhook.](https://dualhook.com/docs/guide-laravel-webhook) [### Shopify WhatsApp Integration\ \ Connect Shopify order events to WhatsApp template messages via Dualhook and Cloud API.](https://dualhook.com/docs/guide-shopify-integration) Need quick definitions while reading the documentation? Browse the [WhatsApp glossary](https://dualhook.com/glossary) . Need to contact Dualhook, report a bug, or ask a setup question? Start with [Support, Contact, and Bug Reports](https://dualhook.com/docs/contact-support) . --- # About Dualhook | Dualhook Browse Docs About Dualhook ============== What Dualhook is, where it fits in the WhatsApp ecosystem, and how it works. Looking for the high-level overview first? Start with [WhatsApp Business API](https://dualhook.com/whatsapp-business-api) and then return here for the implementation details. WhatsApp Solutions Overview[](https://dualhook.com/docs/about#whatsapp-solutions-overview) ------------------------------------------------------------------------------------------- WhatsApp has multiple products that serve different use cases. ### WhatsApp Messenger[](https://dualhook.com/docs/about#whatsapp-messenger) The consumer app for personal communication between individuals. It is not intended for business operations at scale. ### WhatsApp Business App[](https://dualhook.com/docs/about#whatsapp-business-app) The business app for small teams that need business profiles, quick replies, labels, and basic workflows. With [Coexistence](https://dualhook.com/docs/coexistence) enabled, this app can be used alongside Cloud API on the same number. ### WhatsApp Business Platform (Cloud API)[](https://dualhook.com/docs/about#whatsapp-business-platform-cloud-api) The API platform for programmatic messaging at scale. It is used by product teams, automation systems, and integrations that need webhook-driven workflows. Meta also offers a separate **Marketing Messages API (MM API) for WhatsApp** layered on top of the platform, with its own performance optimizations and conversion-tracking endpoints. Dualhook focuses on Cloud API; MM API send paths are not part of Dualhook's managed surface. Where Dualhook Fits[](https://dualhook.com/docs/about#where-dualhook-fits) --------------------------------------------------------------------------- Dualhook is an official Meta Tech Partner and a privacy-first WhatsApp operations platform for teams using Cloud API and Coexistence. It provides direct webhook routing, template management, health monitoring, and operational tooling without acting as a shared inbox or storing message content. Dualhook configures and monitors coexistence so your number can keep mobile app usage while your backend receives API webhooks directly from Meta. * You keep your own Meta Business assets (WABA, phone numbers, app access). * Meta sends message webhooks directly to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) . * Dualhook handles setup and management metadata — not message content. * Dualhook billing is separate from [Meta messaging charges](https://dualhook.com/docs/pricing) . Core Capabilities[](https://dualhook.com/docs/about#core-capabilities) ----------------------------------------------------------------------- ### Connection and Onboarding[](https://dualhook.com/docs/about#connection-and-onboarding) * [Embedded Signup](https://dualhook.com/docs/embedded-signup) support for connection setup * [WABA](https://dualhook.com/docs/whatsapp-business-accounts) and phone number ID discovery * Guided webhook URL and verify token configuration ### Webhook Routing[](https://dualhook.com/docs/about#webhook-routing) * [Webhook Override](https://dualhook.com/docs/webhook-override) configuration for direct Meta-to-customer endpoint message delivery * Webhook testing and delivery monitoring * Management-event visibility in the Dualhook dashboard ### Template and Number Operations[](https://dualhook.com/docs/about#template-and-number-operations) * [Template](https://dualhook.com/docs/templates) sync, create, edit, duplicate, and delete * [Health signals](https://dualhook.com/docs/health-monitoring) such as `AVAILABLE`, `LIMITED`, and `BLOCKED` * [Heartbeat](https://dualhook.com/docs/heartbeat) reminders using a 13-day safety threshold before Meta's approximate 14-day inactivity window ### Compliance and Privacy[](https://dualhook.com/docs/about#compliance-and-privacy) * Direct inbound webhook routing and no message content storage; supported outbound requests transit the no-retention runtime * Operational metadata [retention controls](https://dualhook.com/docs/compliance) (7, 30, or 90 days) * CSV export for webhook operational logs Getting Help from Dualhook[](https://dualhook.com/docs/about#getting-help-from-dualhook) ----------------------------------------------------------------------------------------- Dualhook includes an in-app support chat inside the dashboard so teams can ask questions, report bugs, and follow up without leaving the product. * Open the support bubble in the dashboard to start a conversation. * Use it for onboarding issues, webhook routing questions, billing questions, and bug reports. * Attach screenshots or PDFs when the issue is easier to explain visually. For the full workflow, see [Support, Contact, and Bug Reports](https://dualhook.com/docs/contact-support) . For privacy or legal requests, use the contact path described in the [Privacy Policy](https://dualhook.com/privacy) . Responsibility Split[](https://dualhook.com/docs/about#responsibility-split) ----------------------------------------------------------------------------- | Area | Owner | | --- | --- | | Message transport and delivery pricing | Meta | | Coexistence configuration and operational dashboarding | Dualhook | | Webhook endpoint logic, message storage, business workflows | Customer system | Webhooks in Practice[](https://dualhook.com/docs/about#webhooks-in-practice) ----------------------------------------------------------------------------- Webhooks are the backbone of WhatsApp Cloud API operations. Inbound messages and delivery statuses are sent asynchronously from Meta. In a Dualhook setup: * Message events go directly to your webhook endpoint. * Dualhook primarily handles management events required for dashboard and health visibility. For details, see [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) . What Dualhook Is Not[](https://dualhook.com/docs/about#what-dualhook-is-not) ----------------------------------------------------------------------------- * Official Meta Tech Partner operations layer with direct Meta routing * Not a billing intermediary for Meta message charges * Not a conversation archive for message bodies or media * Not a replacement for your backend message processor Related ------- * [Quick Start](https://dualhook.com/docs/getting-started) Set up Dualhook and connect your first WhatsApp Business Account in minutes. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) How Coexistence mode works: using WhatsApp Business App and Cloud API on the same number. * [Support, Contact, and Bug Reports](https://dualhook.com/docs/contact-support) How to contact Dualhook support, use the in-app support chat, report bugs, attach screenshots or PDFs, and know when email is the right path. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Dualhook Quick Start: Connect Your First WhatsApp Business Account | Dualhook Browse Docs Quick Start =========== Set up Dualhook and connect your first WhatsApp Business Account in minutes. Looking for the high-level overview first? Start with [WhatsApp Business API](https://dualhook.com/whatsapp-business-api) and then return here for the implementation details. Quick Start[](https://dualhook.com/docs/getting-started#quick-start) --------------------------------------------------------------------- Dualhook can be connected in a few minutes if your webhook endpoint is ready. 1. Create or select your organization in Dualhook. 2. Click **New Connection**. 3. Enter your connection name, HTTPS webhook URL, and webhook verify token. 4. Complete [Meta Embedded Signup](https://dualhook.com/docs/embedded-signup) . 5. Run the **Test Ping** to confirm Meta can reach your endpoint. 6. Send a real test message and verify inbound + status webhooks arrive at your server. That is everything. Inbound message-path webhooks now flow directly from Meta to your server. Outbound requests use the connection-scoped Dualhook Runtime API; management events (template updates, quality changes, account alerts) are handled by Dualhook so you can monitor them from the dashboard. Organization and Connection Model[](https://dualhook.com/docs/getting-started#organization-and-connection-model) ----------------------------------------------------------------------------------------------------------------- All data in Dualhook is organization-scoped. A connection binds your Meta IDs and webhook settings together: * `waba_id` — your WhatsApp Business Account * `phone_number_id` — the specific sending number * `webhook_url` — where Meta delivers message webhooks * `webhook_verify_token` — used by Meta to verify your endpoint Prerequisites[](https://dualhook.com/docs/getting-started#prerequisites) ------------------------------------------------------------------------- Before connecting, make sure you have: * An active [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) and [WABA](https://dualhook.com/docs/whatsapp-business-accounts) * A phone number eligible for your [coexistence](https://dualhook.com/docs/coexistence) or API setup * A public HTTPS webhook endpoint * A verify token (we recommend a long random value, at least 16 characters) Dualhook validates that the webhook URL uses HTTPS and that the verify token is at least 16 characters. Test Ping[](https://dualhook.com/docs/getting-started#test-ping) ----------------------------------------------------------------- Dualhook includes a synthetic webhook connectivity test. When you click **Test Ping**, Dualhook sends a payload like this to your endpoint: { "event": "dualhook.test_ping", "source": "dualhook.com", "timestamp": "2026-02-28T12:00:00Z", "organization_id": "", "connection_id": "", "message": "This is a test payload from Dualhook. Your webhook is working!" } Headers included with the test: * `X-Dualhook-Event: test_ping` * `X-Dualhook-Timestamp: ` Go-Live Checklist[](https://dualhook.com/docs/getting-started#go-live-checklist) --------------------------------------------------------------------------------- Before production traffic: 1. Confirm [webhook override](https://dualhook.com/docs/webhook-override) is active for your connection. 2. Confirm webhook verification and inbound webhook POST payload validation are working. 3. Confirm your receiver returns a fast `HTTP 200` and processes asynchronously. 4. Confirm idempotency and retry handling for duplicates and timeouts. 5. Confirm [heartbeat](https://dualhook.com/docs/heartbeat) owner and operating routine are assigned. Support and Escalation[](https://dualhook.com/docs/getting-started#support-and-escalation) ------------------------------------------------------------------------------------------- If setup stalls or a production readiness check fails, use the in-app support chat in the Dualhook dashboard. Include the connection name, failing step, expected result, actual result, and any relevant screenshots. For webhook issues, it also helps to mention whether the problem is verification, override routing, or downstream processing. Use [Support, Contact, and Bug Reports](https://dualhook.com/docs/contact-support) for the recommended reporting format. Related ------- * [Embedded Signup](https://dualhook.com/docs/embedded-signup) How Dualhook uses Meta's Embedded Signup flow to connect WhatsApp Business Accounts via OAuth. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Embedded Signup: featureType, sessionInfoVersion and Coexistence Onboarding | Dualhook Browse Docs Embedded Signup =============== How Dualhook uses Meta's Embedded Signup flow to connect WhatsApp Business Accounts via OAuth. v4 Configuration Requirements[](https://dualhook.com/docs/embedded-signup#v4-configuration-requirements) --------------------------------------------------------------------------------------------------------- Dualhook uses a custom **Facebook Login for Business v4** configuration with the **WhatsApp Embedded Signup** variation. The configuration must be created and reviewed in Meta's Embedded Signup Builder with: * the Cloud API product selected in the v4 configuration * WhatsApp Business Accounts and WhatsApp phone numbers as the only selected assets * a Business Integration System User business token with never-expiring access * only `whatsapp_business_management` and `whatsapp_business_messaging` * Advanced Access for `public_profile`, `whatsapp_business_management`, and `whatsapp_business_messaging` * no `business_management`, ad account, Page, Instagram, dataset, catalog, or Marketing Messages Lite selection * `account_update` subscribed on Dualhook's app-level WhatsApp webhook configuration * Client OAuth Login, Web OAuth Login, Enforce HTTPS, Embedded Browser OAuth Login, Strict Mode for redirect URIs, and Login with the JavaScript SDK enabled * only approved HTTPS hostnames in Allowed Domains and exact HTTPS callback locations in Valid OAuth Redirect URIs `NEXT_PUBLIC_META_CONFIG_ID` must remain on the current accepted configuration until the replacement configuration passes both a sandbox Cloud API onboarding and a sandbox Coexistence onboarding. For Coexistence, confirm that the ordinary WABA selection screen is replaced by the screen offering connection of an existing WhatsApp Business app account. Also test the chat-history opt-in branch before switching production. How Dualhook Uses Embedded Signup[](https://dualhook.com/docs/embedded-signup#how-dualhook-uses-embedded-signup) ----------------------------------------------------------------------------------------------------------------- Dualhook uses Meta's OAuth popup flow to create connections and obtain a short-lived exchangeable authorization code. The code is sent to Dualhook's server immediately and is never written to browser logs. The client-side flow calls `FB.login(...)` with: * `config_id` — your Login for Business configuration * `response_type: "code"` — returns an authorization code instead of a token * `override_default_response_type: true` * `extras: { setup: {}, featureType: "whatsapp_business_app_onboarding" }` — v4 uses the Builder configuration for product assets and permissions, while Meta still requires this launch selector to offer WhatsApp Business app user onboarding Dualhook does not send the legacy `sessionInfoVersion` or generic `feature: "whatsapp_embedded_signup"` launch overrides. The Coexistence completion event itself still reports session payload `version: 3` and uses `FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING`. Dualhook listens for the popup's `postMessage` session info and captures: * `waba_id` — the WhatsApp Business Account ID * `phone_number_id` — the specific phone number ID when Meta includes it; the Coexistence completion payload can omit it, so Dualhook uses server-side discovery featureType, sessionInfoVersion and whatsapp\_business\_app\_onboarding[](https://dualhook.com/docs/embedded-signup#featuretype-sessioninfoversion-and-whatsapp_business_app_onboarding) ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- These three names appear in Meta's Embedded Signup examples and in most reports of a Coexistence popup that never shows the "connect an existing WhatsApp Business app account" screen. They are `extras` values passed to `FB.login`, not permissions and not Builder configuration settings. | Name | What it does | What Dualhook sends | | --- | --- | --- | | `featureType: "whatsapp_business_app_onboarding"` | Launch selector that makes the popup offer the WhatsApp Business app (Coexistence) path instead of only the new-WABA path. | Sent on every launch. Still required with a v4 configuration. | | `sessionInfoVersion: "3"` | v2-era override asking the popup to post session info in the version 3 format. | Not sent. A v4 configuration returns version 3 session info without it. | | `feature: "whatsapp_embedded_signup"` | Generic launch feature from older v1/v2 examples. | Not sent. | FB.login( (response) => { // response.authResponse.code is exchanged server-side }, { config_id: "", response_type: "code", override_default_response_type: true, extras: { setup: {}, featureType: "whatsapp_business_app_onboarding", }, }, ); Symptoms and the usual cause: * The Coexistence screen never appears and the popup only offers creating a new WABA: `featureType` is missing from `extras`, or the configuration was not built with Coexistence enabled. * The popup completes but your listener never receives `FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING`: the `postMessage` listener filters on the wrong origin, or it parses `event.data` before checking that it is the `WA_EMBEDDED_SIGNUP` message. * Session info arrives with `version: 3` but no `phone_number_id`: expected for Coexistence; complete the number through server-side discovery, as in [Fallback Discovery](https://dualhook.com/docs/embedded-signup#fallback-discovery) . Server-Side Completion[](https://dualhook.com/docs/embedded-signup#server-side-completion) ------------------------------------------------------------------------------------------- After receiving the authorization code, Dualhook: 1. Exchanges the code for a customer-scoped **Business Integration System User access token** using Dualhook's app credentials. No app-secret proof applies to this exchange because an access token does not exist yet. The returned token is stored encrypted on the server and is never returned to the customer (see [WhatsApp Business API Permissions](https://dualhook.com/docs/whatsapp-business-api-permissions#graph-api-permissions-oauth-scopes) ). 2. Calls `debug_token` to capture the system user ID and the granular OAuth scopes attached to the token (which WABA `target_ids` it has `whatsapp_business_management` and `whatsapp_business_messaging` access to). This request is proof-aware using the authorizing app access token, not the customer token being inspected. 3. Stores token lifecycle metadata (`tokenIssuedAt`, `tokenExpiresAt`) so Dualhook can warn before expiry. Existing expiring BISU tokens require customer reauthorization; Dualhook does not apply the ordinary System User refresh flow to BISU tokens. 4. Creates a connection draft or full connection. 5. Subscribes Dualhook's app to the WABA and configures [Webhook Override](https://dualhook.com/docs/webhook-override) (best effort). All subsequent native Graph reads and mutations use Dualhook's server-only proof transport. The browser SDK, customer, and webhook receiver never receive or construct `appsecret_proof`. After setup, customers can use the selected connection's `dh_live_...` key for the [allowlisted Graph-compatible Runtime API](https://dualhook.com/docs/runtime-api-reference) . The runtime always derives phone and WABA scope from this stored Embedded Signup selection. A caller cannot replace those IDs, submit a different Meta token, or change webhook subscriptions/callbacks through the runtime. Fallback Discovery[](https://dualhook.com/docs/embedded-signup#fallback-discovery) ----------------------------------------------------------------------------------- If the popup session info is incomplete (for example, the `postMessage` did not arrive), Dualhook falls back to Graph API discovery using the `debug_token` granular scopes captured above: 1. For each WABA `target_id` returned by `debug_token`, fetch WABA name and `owner_business_info`. 2. If granular scopes are empty, fall back to `/me/businesses` and enumerate owned [WABAs](https://dualhook.com/docs/whatsapp-business-accounts) . 3. Enumerate [phone numbers](https://dualhook.com/docs/phone-numbers) under each candidate WABA. If multiple WABAs or numbers are discovered, Dualhook presents the options and finalizes setup with your selection. Common Failure Modes[](https://dualhook.com/docs/embedded-signup#common-failure-modes) --------------------------------------------------------------------------------------- * **Meta finished but no Dualhook connection appears** — follow [Connection Not Appearing After Embedded Signup](https://dualhook.com/docs/connection-not-appearing) for the clean-browser retry and recovery-banner behavior. * **Popup blocked by browser** — allow popups for Dualhook and retry. * **User cancels before code return** — restart the connection flow. * **Token exchange failure** — verify your app credentials and `redirect_uri`. * **No discoverable WABA/phone under granted access** — confirm the correct [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) was selected during the popup. If Meta displays a numbered or textual error inside the popup, search the [Embedded Signup Errors](https://dualhook.com/docs/embedded-signup-errors) index before deleting or recreating any asset. Related ------- * [WhatsApp Embedded Signup Guide](https://dualhook.com/docs/whatsapp-embedded-signup-guide) The vendor-neutral implementer's guide to Meta's Embedded Signup flow: prerequisites, the exact client and server calls, Coexistence, v4, and what breaks in production. * [Quick Start](https://dualhook.com/docs/getting-started) Set up Dualhook and connect your first WhatsApp Business Account in minutes. * [Connection Not Appearing After Embedded Signup](https://dualhook.com/docs/connection-not-appearing) Why Meta can finish linking while no Dualhook connection appears, and how to retry the browser handoff safely. * [Embedded Signup Errors](https://dualhook.com/docs/embedded-signup-errors) Searchable Meta Embedded Signup error index with safe first actions for business, WABA, phone, OTP, sharing, and account restrictions. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Embedded Signup: Implementation Guide | Dualhook Browse Docs WhatsApp Embedded Signup Guide ============================== The vendor-neutral implementer's guide to Meta's Embedded Signup flow: prerequisites, the exact client and server calls, Coexistence, v4, and what breaks in production. Looking for the high-level overview first? Start with [WhatsApp Business API](https://dualhook.com/whatsapp-business-api) and then return here for the implementation details. This is the vendor-neutral implementer's guide to Meta's WhatsApp Embedded Signup: what the flow is, what you must configure before it will run, the exact client and server calls, and the parts that break in production. For how Dualhook itself drives the flow, see [Embedded Signup](https://dualhook.com/docs/embedded-signup) . _Verified against Meta's WhatsApp Business Platform documentation on 14 August 2026._ Direct Answer[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#direct-answer) ---------------------------------------------------------------------------------------- **WhatsApp Embedded Signup is Meta's OAuth popup flow that lets a customer connect their own WhatsApp Business Account and phone number to your app in a few clicks, without ever handling an access token themselves.** You launch it from your own UI with the Facebook JavaScript SDK. The customer logs into Meta, picks or creates a Meta Business Portfolio, a [WhatsApp Business Account](https://dualhook.com/docs/whatsapp-business-accounts) , and a [phone number](https://dualhook.com/docs/phone-numbers) , and approves your app's access. Meta returns a short-lived authorization code to your page and, separately, a `postMessage` carrying the `waba_id` and usually the `phone_number_id`. Your server exchanges that code for a customer-scoped **Business Integration System User (BISU)** access token, subscribes your app to the WABA, and configures webhooks. Embedded Signup is the only supported way for a Tech Provider, Tech Partner, or Solution Partner to onboard a customer's WhatsApp assets at scale. There is no supported manual alternative for third-party onboarding: token copying by hand is a self-serve, single-business pattern, not an onboarding product. What Embedded Signup Actually Is[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#what-embedded-signup-actually-is) ------------------------------------------------------------------------------------------------------------------------------ Strip away the marketing and Embedded Signup is three things bolted together: 1. **A Facebook Login for Business configuration** that declares which assets you want (WhatsApp Business Accounts and phone numbers), which permissions you want (`whatsapp_business_management`, `whatsapp_business_messaging`), and what kind of token comes out. 2. **A hosted multi-step wizard** that Meta renders inside a popup. Business portfolio selection, WABA selection or creation, phone number entry, display name, verification code, and — in the Coexistence variation — the "connect your existing WhatsApp Business app account" branch. 3. **Two independent return channels.** The OAuth authorization `code` comes back through the `FB.login` callback. The IDs of what the customer actually selected come back through a `window.postMessage` from `facebook.com`. These are separate and can arrive out of order, or one without the other. Nearly every hard onboarding bug in this flow traces back to treating them as one channel. The resulting token is a **Business Integration System User token**: it belongs to the customer's business, is scoped to the assets they granted, and is issued to your app. It is not your app token, and it is not a user token. It should never be shown to the customer or embedded in a browser. Prerequisites[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#prerequisites) ---------------------------------------------------------------------------------------- Before the popup will open at all: | Requirement | Where | Notes | | --- | --- | --- | | Meta app with the WhatsApp product added | Meta App Dashboard | Business-type app | | Facebook Login for Business v4 configuration | App Dashboard, Embedded Signup Builder | The `config_id` you pass to `FB.login` | | `whatsapp_business_management` | App permissions | Manage WABAs, templates, phone numbers | | `whatsapp_business_messaging` | App permissions | Send and receive messages | | Advanced Access for both scopes plus `public_profile` | App Review | Standard Access only works for people with a role on your app | | Verified Meta Business Portfolio | Meta Business Settings | Your own portfolio, not the customer's | | HTTPS domain in Allowed Domains | Facebook Login settings | Exact host, no wildcards under Strict Mode | | Exact HTTPS callback in Valid OAuth Redirect URIs | Facebook Login settings | Must match byte for byte | | A live webhook endpoint | Your infrastructure | Must answer Meta's verification challenge before you can route anything | Two of these fail quietly and cost people days: * **Advanced Access.** With Standard Access the popup runs fine for you and fails for every real customer. Test with an account that has no role on your app. * **Strict Mode redirect URIs.** Meta compares the redirect URI literally, including trailing slashes and query strings. The Flow, End to End[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#the-flow-end-to-end) ----------------------------------------------------------------------------------------------------- 1. Your customer clicks **Connect WhatsApp** in your product. 2. You collect anything you need up front — for example the customer's own webhook URL and verify token, if you intend to route webhooks directly to them. 3. You call `FB.login` with your `config_id` and `response_type: "code"`. Meta opens the popup. 4. The customer authenticates, picks a business portfolio, picks or creates a WABA, and adds or selects a phone number. 5. Meta posts a `WA_EMBEDDED_SIGNUP` message to your page containing `waba_id` and, in most flows, `phone_number_id`. 6. The `FB.login` callback fires with `authResponse.code`. 7. Your server exchanges the code for a BISU token, subscribes your app to the WABA, configures webhooks, and registers the number. Steps 5 and 6 race. Handle that explicitly — see [Step 4](https://dualhook.com/docs/whatsapp-embedded-signup-guide#step-4-capture-the-session-info-message) . Step 1: Create the Login for Business v4 Configuration[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#step-1-create-the-login-for-business-v4-configuration) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- In the Meta App Dashboard, open the Embedded Signup Builder and create a **Facebook Login for Business v4** configuration with the **WhatsApp Embedded Signup** variation. Configure it with: * the Cloud API product selected * WhatsApp Business Accounts and WhatsApp phone numbers as the selected assets * a Business Integration System User token with **never-expiring** access * exactly two permissions: `whatsapp_business_management` and `whatsapp_business_messaging` * nothing else selected — no `business_management`, no ad accounts, no Pages, no Instagram, no datasets, no catalogs, no Marketing Messages Lite Requesting scopes you do not need is the most common reason an App Review submission stalls, and every extra asset type you select adds a consent screen your customer has to read. Under **Facebook Login → Settings**, enable Client OAuth Login, Web OAuth Login, Enforce HTTPS, Embedded Browser OAuth Login, Strict Mode for redirect URIs, and Login with the JavaScript SDK. The configuration produces a **configuration ID**. That is the `config_id` your client code passes. Treat it as environment configuration, not a constant: you will eventually need to swap it, and you want that to be a deploy, not a code change. Step 2: Load the Facebook JavaScript SDK[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#step-2-load-the-facebook-javascript-sdk) --------------------------------------------------------------------------------------------------------------------------------------------- window.fbAsyncInit = function () { FB.init({ appId: process.env.NEXT_PUBLIC_META_APP_ID, autoLogAppEvents: true, xfbml: true, version: "v25.0", }); }; The SDK must be loaded before the customer can click your button. If `window.FB` is undefined when they click, fail loudly with a "refresh and try again" message rather than silently doing nothing — a dead button is indistinguishable from a broken product. Step 3: Launch the Popup with FB.login[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#step-3-launch-the-popup-with-fblogin) ---------------------------------------------------------------------------------------------------------------------------------------- FB.login( (response) => { if (response.authResponse?.code) { // Send the code to your server. Never exchange it in the browser: // the exchange requires your app secret. void completeSignup(response.authResponse.code); return; } // status === "unknown" covers cancellation, popup blocking, and // documented Embedded Signup errors. Classify with the postMessage. handleAbandonedSignup(); }, { config_id: process.env.NEXT_PUBLIC_META_CONFIG_ID, response_type: "code", override_default_response_type: true, extras: { setup: {}, // Coexistence only. Omit featureType for a plain Cloud API flow. featureType: "whatsapp_business_app_onboarding", }, } ); What each field does: | Field | Purpose | | --- | --- | | `config_id` | The Login for Business v4 configuration from Step 1 | | `response_type: "code"` | Returns an authorization code instead of a browser access token | | `override_default_response_type: true` | Required, or the SDK falls back to its default token response | | `extras.setup` | Prefill object. An empty object means "ask the customer for everything" | | `extras.featureType` | Launch selector. `whatsapp_business_app_onboarding` offers the Coexistence branch | Under v4, the Builder configuration — not the JavaScript call — controls products, assets, and permissions. The generic `feature: "whatsapp_embedded_signup"` override belongs to older versions and does nothing here. `sessionInfoVersion` is a more nuanced case. Meta's Coexistence page still shows `"sessionInfoVersion": "3"` in its sample `extras` object, but the v4 page makes no mention of it, and a v4 configuration works without it — Dualhook's own production integration omits it and still receives the Coexistence completion payload, which reports `version: 3` regardless. Treat it as vestigial rather than required, and if you are debugging an unexplained flow difference, adding it back is a cheap thing to test. The one thing v4 genuinely does **not** move into the Builder is the launch selector. Meta's Version 4 page is explicit that "onboarding WhatsApp Business app users continues to be supported through the `feature_type` parameter," so `featureType: "whatsapp_business_app_onboarding"` still has to be passed in `extras` for the Coexistence screen to appear, even on a v4 configuration that has Coexistence enabled. A bare `extras: { setup: {} }` runs a clean Cloud API onboarding and quietly hides the Coexistence option — which is the correct call if you do not support Coexistence, and a silent bug if you do. `FB.login` must be called synchronously inside the click handler. If you `await` anything first, the browser treats the popup as unsolicited and blocks it. Step 4: Capture the Session Info Message[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#step-4-capture-the-session-info-message) --------------------------------------------------------------------------------------------------------------------------------------------- Meta sends the selected asset IDs through `postMessage`, not through the login callback. const listener = (event) => { if ( event.origin !== "https://www.facebook.com" && event.origin !== "https://web.facebook.com" ) { return; } let data; try { data = typeof event.data === "string" ? JSON.parse(event.data) : event.data; } catch { return; } if (data.type !== "WA_EMBEDDED_SIGNUP") return; const event_name = String(data.event ?? "").toUpperCase(); if (data.data?.waba_id) { sessionInfo = { waba_id: data.data.waba_id, phone_number_id: data.data.phone_number_id ?? null, }; } if (event_name === "FINISH" || event_name === "FINISH_ONLY_WABA" || event_name === "FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING") { // Meta linked the account. The code may still be in flight. } else if (event_name === "CANCEL") { // data.data.current_step tells you where they dropped out. } else if (event_name === "ERROR") { // data.data.error_id / error_message / session_id } }; window.addEventListener("message", listener); Five rules that separate a reliable integration from a flaky one: 1. **Always check `event.origin`.** Any page can post a message to yours. Accept only `https://www.facebook.com` and `https://web.facebook.com`. 2. **Expect `phone_number_id` to be missing.** The Coexistence completion event routinely omits it. Fall back to server-side discovery rather than failing the connection. 3. **Do not treat `FINISH` as success.** `FINISH` means Meta linked the account. It does not mean the authorization code reached your server. Those are different outcomes with different recoveries. 4. **Do not treat `PARTNER_ADDED` or `PARTNER_APP_INSTALLED` as completion.** They fire mid-flow. They are corroborating evidence, not proof. 5. **Give the message a grace period.** When the callback fires first, poll for the session info for a few seconds before giving up on it. Do not log the raw event. It can carry localized error text and selected-business payloads you have no reason to retain. Store the numeric error ID, Meta's session ID, and the failed step, and nothing else. Step 5: Exchange the Code for a Business Token[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#step-5-exchange-the-code-for-a-business-token) --------------------------------------------------------------------------------------------------------------------------------------------------------- Server-side only. This call needs your app secret. curl -X GET "https://graph.facebook.com/v25.0/oauth/access_token" \ -d "client_id=" \ -d "client_secret=" \ -d "code=" You get back a **Business Integration System User access token** scoped to the assets the customer granted. Under a correctly built v4 configuration it does not expire. Immediately after the exchange, call `debug_token` to find out what you actually received: curl -X GET "https://graph.facebook.com/v25.0/debug_token?input_token=" \ -H "Authorization: Bearer " The response's `granular_scopes` tells you which WABA `target_ids` carry `whatsapp_business_management` and `whatsapp_business_messaging`. This is your ground truth when the `postMessage` never arrived, and it is the first thing to check when sends later fail with [error 200](https://dualhook.com/docs/whatsapp-error-200) or [error 10](https://dualhook.com/docs/whatsapp-error-10) . Handling rules: * Store the token encrypted at rest. Never return it to the browser, never show it in your UI, never put it in a log line. * Record `issued_at` and `expires_at` so you can warn before a non-permanent token dies. A BISU token is not refreshable through the ordinary System User refresh flow; recovery means the customer re-runs Embedded Signup. * Once you hold a token, every subsequent Graph call should be sent with `appsecret_proof`. Without it, a leaked token is usable by anyone who has it. * The code exchange itself takes no `appsecret_proof` — there is no token yet. Authorization codes are single-use and short-lived. If your server retries the exchange after a network timeout, the second attempt fails; treat it as terminal and have the customer restart. Step 6: Subscribe Your App and Route Webhooks[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#step-6-subscribe-your-app-and-route-webhooks) ------------------------------------------------------------------------------------------------------------------------------------------------------- A token alone delivers nothing. Two more calls: # 1. Subscribe your app to the customer's WABA curl -X POST "https://graph.facebook.com/v25.0//subscribed_apps" \ -H "Authorization: Bearer " # 2. Optional: override the callback so Meta delivers straight to the customer curl -X POST "https://graph.facebook.com/v25.0//subscribed_apps" \ -H "Authorization: Bearer " \ -d "override_callback_uri=https://customer.example.com/webhooks/whatsapp" \ -d "verify_token=" Order matters. Overriding before subscribing returns Graph error 100 with "Before override the current callback uri" — see [Webhook Error 100](https://dualhook.com/docs/whatsapp-error-100-webhook-override) . Whichever endpoint receives the traffic must already answer Meta's verification challenge. Meta sends a `GET` with `hub.mode=subscribe`, `hub.verify_token`, and `hub.challenge`. Compare the verify token, then return the raw `hub.challenge` value as plain text with HTTP 200 — not JSON, not quoted, not wrapped. app.get("/webhooks/whatsapp", (req, res) => { const mode = req.query["hub.mode"]; const token = req.query["hub.verify_token"]; const challenge = req.query["hub.challenge"]; if (mode === "subscribe" && token === process.env.VERIFY_TOKEN) { return res.status(200).send(challenge); } return res.sendStatus(403); }); Set this endpoint up **before** you run Embedded Signup, not after. Meta verifies at configuration time, and a 404 at that moment turns a clean onboarding into a support ticket. [Webhook Override](https://dualhook.com/docs/webhook-override) is what makes direct routing possible: message-path fields go from Meta to the customer's own server, while management events stay on your app-level callback. See the [webhook subscription field reference](https://dualhook.com/docs/webhooks) for which fields are overridable. Step 7: Register the Phone Number[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#step-7-register-the-phone-number) ------------------------------------------------------------------------------------------------------------------------------- A number selected in the popup is not yet usable by Cloud API. Register it: curl -X POST "https://graph.facebook.com/v25.0//register" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"messaging_product":"whatsapp","pin":""}' The `pin` is the number's two-step verification PIN. If two-step verification was never set, you are setting it here. If it was set and you do not know it, registration fails and the customer must reset it in WhatsApp Manager. Two failures are common enough to name: * **Subcode 2388001** — Meta does not publicly document this subcode. It is commonly seen when a number is still attached to WhatsApp, the WhatsApp Business app, or a previous provider, but confirm the actual ownership state before changing anything. Use Coexistence if the business must keep the app, and do not delete or deregister an account as a generic fix. * **[Error 133016](https://dualhook.com/docs/whatsapp-error-133016) ** — too many registration attempts. Retrying faster makes the cooldown longer. Coexistence: Onboarding WhatsApp Business App Users[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#coexistence-onboarding-whatsapp-business-app-users) ------------------------------------------------------------------------------------------------------------------------------------------------------------------- Most businesses that want the API already use the WhatsApp Business app, and most of them do not want to give it up. [Coexistence](https://dualhook.com/docs/coexistence) is Meta's supported answer: the app and Cloud API run on the same number. Onboarding differences you have to plan for: * Pass `featureType: "whatsapp_business_app_onboarding"` in `extras`. Without it, the customer never sees the "connect your existing WhatsApp Business app account" screen. * The completion event is `FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING`, and its payload can omit `phone_number_id`. Discover the number server-side from the WABA. * The customer completes a QR-code or in-app confirmation step on the phone itself. That is outside your UI — build for the wait. * After the override is accepted, request the one-time contact sync ([`smb_app_state_sync`](https://dualhook.com/docs/webhook-smb-app-state-sync) ) and then chat-history sync ([`history`](https://dualhook.com/docs/webhook-history) ). Both are one-shot and time-sensitive. If one request fails, still attempt the other. * Coexistence adds an ongoing operational requirement: Meta may disconnect after approximately 14 days of primary-device inactivity. Dualhook's [Heartbeat](https://dualhook.com/docs/heartbeat) warns at 13 days as a conservative safety margin. * Messages sent from the app arrive as [`smb_message_echoes`](https://dualhook.com/docs/webhook-smb-message-echoes) , not as `messages`. If the number was previously onboarded by another provider, Embedded Signup can fail before it grants you anything with `#2655093` — see [Error 2655093](https://dualhook.com/docs/whatsapp-error-2655093) . Embedded Signup Versions: v2, v3, and v4[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#embedded-signup-versions-v2-v3-and-v4) ------------------------------------------------------------------------------------------------------------------------------------------- | Version | How it is configured | Status | | --- | --- | --- | | v2 | Assets and permissions declared in the `FB.login` call | Legacy. Meta has set 15 October 2026 as its end of life | | v3 | Required `extras.version: "v3"` to opt in | Transitional | | v4 | Assets, permissions, and products declared in the Embedded Signup Builder configuration | Current | A detail that catches teams out during the audit: if you never explicitly passed `extras.version: "v3"`, you were on v2 regardless of when you built your integration. Opting into v3 was never automatic, so "we built this recently" is not evidence of anything. Check the actual `extras` object you send. Migrating to v4 is not only a code change. You need a **new configuration** built in the Builder with the products and assets selected, plus the code change that stops sending v2-style launch parameters. Ship both together, and validate the new configuration against both a plain Cloud API onboarding and a Coexistence onboarding — including the chat-history opt-in branch — before pointing production at it. A configuration that works for one and silently breaks the other is the normal failure, not the exotic one. Embedded Signup vs Manual Onboarding[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#embedded-signup-vs-manual-onboarding) -------------------------------------------------------------------------------------------------------------------------------------- | | Embedded Signup | Manual setup | | --- | --- | --- | | Who does the work | The customer, in a popup | Your engineers, per customer | | Token handling | Server-side code exchange | Customer copies a token into your UI | | Asset selection | Meta's wizard | Manual ID collection | | Number registration | Part of the flow | A separate Graph call you script | | Webhook setup | Configurable during onboarding | Manual per WABA | | Coexistence support | Yes, via the launch selector | Not available | | Scales to many customers | Yes | No | | Supported by Meta for third-party onboarding | Yes | No | Manual token entry remains reasonable for exactly one case: a single business wiring up its own number for its own backend. The moment there is a second business, you are building an onboarding product, and Embedded Signup is the supported shape. What Goes Wrong, and How to Read It[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#what-goes-wrong-and-how-to-read-it) ----------------------------------------------------------------------------------------------------------------------------------- | Symptom | Likely cause | Where to look | | --- | --- | --- | | Popup never opens | `FB.login` called after an `await`, or SDK not loaded | Call it synchronously in the click handler | | Works for you, fails for customers | Standard Access instead of Advanced Access | App Review | | `status: "unknown"`, no code | Popup blocked, cancelled, or a documented Meta error | Classify using the `postMessage` | | `FINISH` received, no connection created | The code did not return to your server | [Connection Not Appearing](https://dualhook.com/docs/connection-not-appearing) | | Code exchange returns an OAuth error | Redirect URI mismatch, reused code, or wrong app secret | Strict Mode redirect URIs | | `#2655093` in the popup | The number is still shared with a previous partner | [Error 2655093](https://dualhook.com/docs/whatsapp-error-2655093) | | Webhook override rejected with code 100 | Override attempted before subscribing | [Webhook Error 100](https://dualhook.com/docs/whatsapp-error-100-webhook-override) | | Registration fails with subcode 2388001 | Number still attached to WhatsApp, the Business app, or a previous provider | Confirm the ownership state; use Coexistence if the app must stay. Do not delete an account as a generic fix | | Sends fail with 200 or 10 after onboarding | Token lacks the scope for that WABA | `debug_token` granular scopes | The full index is at [Embedded Signup Errors](https://dualhook.com/docs/embedded-signup-errors) ; numeric Cloud API codes are at [WhatsApp Cloud API Error Codes](https://dualhook.com/docs/api-errors) . Implementation Checklist[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#implementation-checklist) -------------------------------------------------------------------------------------------------------------- * Meta app with the WhatsApp product, business type * Login for Business v4 configuration built in the Embedded Signup Builder * Advanced Access approved for `whatsapp_business_management`, `whatsapp_business_messaging`, and `public_profile` * Allowed Domains and Valid OAuth Redirect URIs set exactly, with Strict Mode on * Webhook endpoint live and answering `hub.challenge` before you run the flow * `FB.login` called synchronously with `config_id`, `response_type: "code"`, and `override_default_response_type: true` * `featureType: "whatsapp_business_app_onboarding"` in `extras` if you support Coexistence * Origin-checked `postMessage` listener that tolerates a missing `phone_number_id` * Server-side code exchange, then `debug_token` to record granular scopes * Token encrypted at rest, never returned to the browser, `appsecret_proof` on every later call * `subscribed_apps` before any `override_callback_uri` * Phone number registered with a known two-step PIN * A recovery path for `FINISH` without a resulting connection * Tested end to end with an account that has no role on your app Where Dualhook Fits[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#where-dualhook-fits) ---------------------------------------------------------------------------------------------------- Dualhook is a badged Meta Tech Partner and runs this flow as a product. The Meta app, the v4 configuration, App Review, the code exchange, token encryption, `debug_token` scope capture, `subscribed_apps`, Webhook Override, and the recovery path for a `FINISH` that never returned an authorization are all handled for you. The part worth knowing about is where the traffic goes afterwards. Dualhook asks for your webhook URL and verify token _before_ the popup opens and configures a WABA-level override at completion, so message-path webhooks go from Meta straight to your endpoint. Dualhook is not in the message path and does not store message content. See [Webhook Override](https://dualhook.com/docs/webhook-override) and [WhatsApp API Privacy](https://dualhook.com/whatsapp-api-privacy) . If you are building the flow yourself, the checklist above is the whole job. If you would rather not own App Review, configuration migrations, and popup-error triage, that is what Dualhook is for. FAQ[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#faq) -------------------------------------------------------------------- **What is WhatsApp Embedded Signup?** Meta's OAuth-based onboarding flow. It lets a customer connect their own WhatsApp Business Account and phone number to a third-party app through a popup. The customer authorizes access, Meta returns a short-lived authorization code, and the integrator exchanges it server-side for a Business Integration System User access token. The customer never handles the token. **Who can use Embedded Signup?** Any Meta partner that has added the WhatsApp product to a business-type app and holds Advanced Access for `whatsapp_business_management` and `whatsapp_business_messaging` — Tech Providers, Tech Partners, and Solution Partners. It is the supported path for onboarding customer-owned WhatsApp assets at scale. **What do I need before I can run the flow?** A Meta app with the WhatsApp product, a Facebook Login for Business v4 configuration created in the Embedded Signup Builder, Advanced Access for the two WhatsApp scopes plus `public_profile`, exact HTTPS entries in Allowed Domains and Valid OAuth Redirect URIs, and a live webhook endpoint that answers Meta's `hub.challenge` verification. **How does the token exchange work?** Meta returns a short-lived authorization code to your page through the `FB.login` callback. You send it to your server, which calls `GET /v25.0/oauth/access_token` with your app ID, app secret, and the code. The response is a Business Integration System User token scoped to the assets the customer granted. Store it encrypted; never return it to the browser. **Why is phone\_number\_id missing from the session info?** Because Meta does not always include it, particularly in the Coexistence completion event `FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING`. Fall back to server-side discovery: call `debug_token`, read the WABA `target_ids` from `granular_scopes`, and enumerate the phone numbers under each WABA. **Does a FINISH event mean the signup succeeded?** No. `FINISH` means Meta linked the account. The authorization code travels on a separate channel and can still fail to reach your server. Treat "Meta linked it but we never got the code" as its own state with its own recovery, rather than as either success or failure. **What is the difference between Embedded Signup v2, v3, and v4?** v2 declared assets and permissions in the `FB.login` call. v3 required an explicit `extras.version: "v3"` opt-in. v4 moves products, assets, and permissions into the Embedded Signup Builder configuration, so the JavaScript call carries only `config_id`, the response type, and the launch selector. Meta has set 15 October 2026 as the end of life for v2. **How do I onboard a business that already uses the WhatsApp Business app?** Pass `featureType: "whatsapp_business_app_onboarding"` in `extras`. That launch selector is what surfaces the Coexistence branch, and it is still required under v4 even when the Builder configuration has Coexistence enabled. Expect the completion event `FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING`, and expect it to sometimes omit the phone number ID. **Can I exchange the authorization code in the browser?** No. The exchange requires your app secret, which must never reach a browser. Post the code to your own server and exchange it there. Codes are single-use and short-lived, so a retried exchange after a timeout will fail. Sources[](https://dualhook.com/docs/whatsapp-embedded-signup-guide#sources) ---------------------------------------------------------------------------- * Meta, [WhatsApp Business Platform Embedded Signup](https://developers.facebook.com/documentation/business-messaging/whatsapp/embedded-signup/overview) * Meta, [Facebook Login for Business](https://developers.facebook.com/documentation/facebook-login/facebook-login-for-business) * Meta, [Embedded Signup version 4](https://developers.facebook.com/documentation/business-messaging/whatsapp/embedded-signup/version-4) * Meta, [Cloud API phone number registration](https://developers.facebook.com/documentation/business-messaging/whatsapp/reference/whatsapp-business-phone-number/register-api) * Meta, [WhatsApp Business Platform webhooks](https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/overview) * Meta, [Onboarding WhatsApp Business app users](https://developers.facebook.com/documentation/business-messaging/whatsapp/embedded-signup/onboarding-business-app-users) All checked 14 August 2026. Meta is the authority for every field name, scope, and date above; re-verify before you commit a migration date to a plan. Related ------- * [Embedded Signup](https://dualhook.com/docs/embedded-signup) How Dualhook uses Meta's Embedded Signup flow to connect WhatsApp Business Accounts via OAuth. * [Embedded Signup Errors](https://dualhook.com/docs/embedded-signup-errors) Searchable Meta Embedded Signup error index with safe first actions for business, WABA, phone, OTP, sharing, and account restrictions. * [Connection Not Appearing After Embedded Signup](https://dualhook.com/docs/connection-not-appearing) Why Meta can finish linking while no Dualhook connection appears, and how to retry the browser handoff safely. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) How Coexistence mode works: using WhatsApp Business App and Cloud API on the same number. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Connection Not Appearing After Embedded Signup | Dualhook Browse Docs Connection Not Appearing After Embedded Signup ============================================== Why Meta can finish linking while no Dualhook connection appears, and how to retry the browser handoff safely. You completed Meta's Embedded Signup and may even see Dualhook in the WhatsApp Business app, but no connection appears in the Dualhook dashboard. This usually means Meta completed its side of the link but the final browser handoff did not return to the original Dualhook tab. What Happened[](https://dualhook.com/docs/connection-not-appearing#what-happened) ---------------------------------------------------------------------------------- Embedded Signup has two sides: 1. Meta links the WhatsApp Business Account and grants Dualhook access inside its popup. 2. The popup sends the selected WABA and phone IDs plus a one-time authorization result back to the original Dualhook tab. Dualhook can create the dashboard connection only after the second step reaches it. Popup restrictions, privacy extensions, Brave Shields, blocked cookies, an in-app browser, or closing the original tab can interrupt that return even though Meta already shows the partner link. Dualhook records only operational signup signals. It never stores the one-time OAuth code in the recovery ledger. If a launched attempt remains unfinished, the Connections page may show a **Your WhatsApp connection may need one more try** banner. Retry in a Clean Browser[](https://dualhook.com/docs/connection-not-appearing#retry-in-a-clean-browser) -------------------------------------------------------------------------------------------------------- 1. Use desktop Chrome or Firefox in a normal window or a fresh browser profile. 2. Allow popups and cookies for `dualhook.com`, `facebook.com`, and `web.facebook.com`. 3. Temporarily disable ad blockers, content blockers, privacy extensions, and Brave Shields. 4. Do not start from an in-app browser inside WhatsApp, Instagram, an email app, or another mobile app. 5. Keep the original Dualhook tab open while completing the Meta popup and the WhatsApp Business app confirmation. 6. Return to the original tab and wait for Dualhook's finalizing screen to finish. If the dashboard recovery banner is visible, use **Retry connection** there. If Meta instead shows a numbered error, check the [Embedded Signup error index](https://dualhook.com/docs/embedded-signup-errors) . What Not to Delete[](https://dualhook.com/docs/connection-not-appearing#what-not-to-delete) -------------------------------------------------------------------------------------------- Do not delete the phone number, WABA, Meta Business Portfolio, or WhatsApp Business account just because the Dualhook connection is missing. Do not deregister the number or reinstall WhatsApp Business as a first troubleshooting step. Those actions solve a different class of problem and can affect service or message history. If Meta says the account is already shared with another partner, follow the dedicated [error #2655093 guide](https://dualhook.com/docs/whatsapp-error-2655093) instead. Billing and Retrying[](https://dualhook.com/docs/connection-not-appearing#billing-and-retrying) ------------------------------------------------------------------------------------------------ Retrying Embedded Signup does not send a WhatsApp message, so it does not create a Meta messaging charge. Meta bills eligible delivered messages; Dualhook billing is a separate subscription described on the [Pricing](https://dualhook.com/docs/pricing) page. A failed browser return with no Dualhook connection row does not create an additional metered Dualhook connection. If the connection already appears in the dashboard, use that connection instead of starting another signup. When to Contact Support[](https://dualhook.com/docs/connection-not-appearing#when-to-contact-support) ------------------------------------------------------------------------------------------------------ If two clean-browser attempts still do not produce a connection, contact us through the in-app support chat. For the normal flow and server-side fallback discovery, see [Embedded Signup](https://dualhook.com/docs/embedded-signup) . For webhook routing after connection, see [Webhook Override](https://dualhook.com/docs/webhook-override) and [Troubleshooting](https://dualhook.com/docs/troubleshooting) . Related ------- * [Embedded Signup](https://dualhook.com/docs/embedded-signup) How Dualhook uses Meta's Embedded Signup flow to connect WhatsApp Business Accounts via OAuth. * [Embedded Signup Errors](https://dualhook.com/docs/embedded-signup-errors) Searchable Meta Embedded Signup error index with safe first actions for business, WABA, phone, OTP, sharing, and account restrictions. * [Troubleshooting](https://dualhook.com/docs/troubleshooting) Common issues and solutions for Dualhook connections, webhooks, and health monitoring. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Pricing](https://dualhook.com/docs/pricing) Dualhook subscription plans, what is included, and how pricing relates to Meta messaging fees. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Pricing | Dualhook Browse Docs Pricing ======= Dualhook subscription plans, what is included, and how pricing relates to Meta messaging fees. Looking for the high-level overview first? Start with [Pricing](https://dualhook.com/pricing) and then return here for the implementation details. Overview[](https://dualhook.com/docs/pricing#overview) ------------------------------------------------------- Dualhook pricing is a monthly subscription based on plan tier and connection limits. Dualhook does **not** charge per WhatsApp message. All plans include a 14-day free trial. A payment method is required at sign-up, but you are not charged until the trial ends. WhatsApp message fees are charged by Meta under the WhatsApp Business Platform. Your total cost has two separate parts: * **Dualhook subscription fee** — our product * **Meta messaging fees** — their platform usage Dualhook Plans[](https://dualhook.com/docs/pricing#dualhook-plans) ------------------------------------------------------------------- | Plan | Price | Included connections | Best fit | | --- | --- | --- | --- | | Developer | $12/month | 1 | Individuals and early projects | | Team | $25/month | 5 | Growing teams | | Agency | $45/month | 10 | Growing teams with more connections | | Enterprise | $99/month | 25, then $3.50 per additional connection | Multi-location businesses and larger organizations | * Team is the most common plan. * Enterprise includes 25 connections and adds $3.50 per connection beyond that. It is a higher connection allowance for a single organization. * Dualhook is for businesses connecting WhatsApp assets they own or directly operate. Reselling connections, multi-tenant SaaS onboarding, and third-party customer provisioning are not permitted on any plan. * All plans include a 14-day free trial. * A payment method is required at sign-up for self-serve checkout. * Checkout may display USD or EUR depending on region. * Upgrades apply immediately. * Downgrades apply at the end of the current billing cycle. What Is Included[](https://dualhook.com/docs/pricing#what-is-included) ----------------------------------------------------------------------- All plans include: * Direct webhook delivery (Meta to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) ) * Webhook testing and monitoring * [Template management](https://dualhook.com/docs/templates) * [Heartbeat monitoring](https://dualhook.com/docs/heartbeat) * In-app support chat for setup questions and bug reports * Privacy-first architecture (no message storage) * Unlimited webhook deliveries Message Charges: Dualhook vs Meta[](https://dualhook.com/docs/pricing#message-charges-dualhook-vs-meta) -------------------------------------------------------------------------------------------------------- Dualhook does not add per-message fees. If you send or receive WhatsApp messages through Cloud API, those usage charges are billed by Meta, not by Dualhook. This remains true on every Dualhook tier. Official Meta Rate Cards[](https://dualhook.com/docs/pricing#official-meta-rate-cards) --------------------------------------------------------------------------------------- | Link | Rate Card Description | | --- | --- | | [Meta USD Rate Card](https://developers.facebook.com/docs/whatsapp/pricing/) | Official USD rates for template messages by country code/market | | [Meta EUR Rate Card](https://developers.facebook.com/docs/whatsapp/pricing/) | Official EUR rates for template messages by country code/market | | [Meta INR Rate Card](https://developers.facebook.com/docs/whatsapp/pricing/) | Official INR rates for template messages by country code/market | | [Meta pricing overview](https://business.whatsapp.com/products/platform-pricing) | Official overview of pricing categories and free windows | * Meta may update rate cards on a quarterly cadence (Jan 1, Apr 1, Jul 1, Oct 1). * Always verify current rates before launching high-volume campaigns. * Questions about official Meta rate cards should go to Meta support. Meta Pricing Model[](https://dualhook.com/docs/pricing#meta-pricing-model) --------------------------------------------------------------------------- Since **July 1, 2025** Meta charges WhatsApp Business Platform usage on a **per-message** basis. The previous conversation-based pricing model is deprecated. * Charges apply only when a **template** message is delivered (`"type":"template"`). * All non-template messages (`text`, `image`, etc.) are free, but can only be sent inside an open 24-hour customer service window (CSW). * Rates depend on the template **category** (marketing, utility, authentication) and the recipient's **country calling code**. * Meta updates rate cards on a quarterly cadence (Jan 1, Apr 1, Jul 1, Oct 1), with at least 1 month notice for rate changes, 3 months for pricing model add-ons, and 6 months for pricing model changes. Those first two bullets describe pricing through **September 30, 2026**. From **October 1, 2026**, Meta will also charge for service messages after a phone number's first 1,000 service messages each month, and for utility templates delivered inside an open CSW. See [WhatsApp Pricing Changes 2026](https://dualhook.com/docs/whatsapp-pricing-changes-2026) for the transition and published October rate-card update. Free vs Billed Messages[](https://dualhook.com/docs/pricing#free-vs-billed-messages) ------------------------------------------------------------------------------------- Whether a message is free or billed is decided by Meta policy and pricing rules, not by Dualhook. Key windows: * **24-hour customer service window (CSW)** — opens when the user messages you. Non-template replies are only allowed inside this window. Utility templates inside an open CSW are free through September 30, 2026, then billed from October 1. * **72-hour free entry point window (FEP)** — opens when you respond within 24 hours to a user who messaged you via a Click-to-WhatsApp ad or a Facebook Page Call-to-Action button (Android/iOS WhatsApp app only — desktop and web are not supported). Marketing, utility, authentication, and service messages are free inside the FEP; Meta Business Agent messages still incur their token charge. | Message type | Through September 30, 2026 | From October 1, 2026 | | --- | --- | --- | | Service message from a person or third-party AI | Free inside an open CSW or FEP | First 1,000 per phone number each month are free; later deliveries are billed at the market's service rate. FEP deliveries remain free | | Utility template | Free inside CSW or FEP; billed outside both | Billed inside or outside CSW; free inside FEP | | Authentication template | Free inside FEP; billed otherwise | Same, at the October rate card | | Marketing template | Free inside FEP; billed otherwise | Same, at the October rate card | | Meta Business Agent message | Charged per token from August 1, including inside FEP | Same; cache tokens are not charged | Meta charges are based on **delivered** messages, not sent attempts. Pricing Object in Status Webhooks[](https://dualhook.com/docs/pricing#pricing-object-in-status-webhooks) --------------------------------------------------------------------------------------------------------- Each message status webhook from Meta carries a `pricing` object that explains exactly how that delivery was billed. Dualhook does not ingest message-status webhooks (they go straight to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) ), but your downstream handler should read this field for accounting: "pricing": { "billable": true, "pricing_model": "PMP", "type": "regular", "category": "" } Common combinations: | `type` | `category` | Meaning | | --- | --- | --- | | `regular` | `marketing`, `utility`, `authentication` | Standard billable template delivery via Cloud API `/messages` | | `regular` | `marketing_lite` | Marketing template delivered via [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api)
`/marketing_messages`. Same per-message billing as `marketing`; the category lets you distinguish send paths in your accounting. | | `regular` | `authentication_international` | Auth template billed under [authentication-international rates](https://dualhook.com/docs/pricing#authentication-international-rates) | | `regular` | `general_purpose_ai` | Non-template message delivered by an AI Provider in an [applicable market](https://dualhook.com/docs/pricing#ai-provider-pricing) | | `free_customer_service` | `service` | Free non-template message inside open CSW | | `free_customer_service` | `utility` | Free utility template inside open CSW | From October 1, billable service messages use `type: regular` with `category: service`. Meta has not yet documented the webhook `type` used when a service message is covered by the new monthly free tier, so use the received `billable` and `type` values rather than treating `category: service` as always free or always billed. `pricing_model` is `PMP` (per-message pricing) for all messages delivered after July 1, 2025. Edge case: a `utility` conversation opened _before_ July 1, 2025 retains `pricing_model: "CBP"` until the conversation closes. Volume Tiers[](https://dualhook.com/docs/pricing#volume-tiers) --------------------------------------------------------------- Meta unlocks lower per-message rates for **utility** and **authentication** templates as monthly volume grows. Marketing templates do not have volume tiers. Key mechanics: * Tiers are aggregated at the **business portfolio** level, across every WABA the portfolio owns. * Tiers are **market–category specific** — e.g. India–authentication, Brazil–utility — each accrues independently. * Only **billed** messages count toward the tier; free utility messages (in CSW or FEP) do not. * Tiers **reset on the 1st of each month** at 12am WABA timezone. * When a tier is reached, only the messages above the threshold get the new rate. Messages already sent in that month stay at the prior rate. Starting October 1, 2025 Meta sends an `account_update` webhook with event `VOLUME_BASED_PRICING_TIER_UPDATE` when a portfolio reaches a new tier. See [Account Update Events](https://dualhook.com/docs/webhook-events#account-update-events) for the payload shape. For intra-month progress, query the `pricing_analytics` field on the WABA. Authentication-International Rates[](https://dualhook.com/docs/pricing#authentication-international-rates) ----------------------------------------------------------------------------------------------------------- A separate, higher rate applies to **authentication** template messages delivered to users in: Egypt, India, Indonesia, Malaysia, Nigeria, Pakistan, Saudi Arabia, South Africa, and the United Arab Emirates — but only when the sending business is **based outside** that country. Eligibility is automatic: a business that delivers more than **750,000** messages outside customer service windows in a moving 30-day period to users in any of these countries is deemed eligible. Eligibility is permanent and Meta gives 30 days notice via email and an `account_update` webhook with event `AUTH_INTL_PRICE_ELIGIBILITY_UPDATE` before rates begin. To check eligibility and start times for a WABA: curl 'https://graph.facebook.com//?fields=auth_international_rate_eligibility' \ -H 'Authorization: Bearer ' To check Meta's record of the business's primary location: curl 'https://graph.facebook.com//?fields=primary_business_location' \ -H 'Authorization: Bearer ' When billed under this rate, the status webhook `pricing.category` is `authentication_international`. Charges are still per delivered message, just at a higher list rate. AI Provider Pricing[](https://dualhook.com/docs/pricing#ai-provider-pricing) ----------------------------------------------------------------------------- Meta introduced a separate charge for non-template messages sent by businesses it classifies as "AI Providers" in parts of Europe during February and March 2026. Effective **May 13, 2026**, Meta stopped charging AI Providers for those messages to EU and EEA users; charges to users in Brazil remain in effect. The European Commission also imposed interim measures on June 9 requiring Meta to preserve free WhatsApp Business API access for third-party general-purpose AI assistants in the EU while its investigation continues. When billed under this policy, status webhooks carry `pricing.category: general_purpose_ai`, and the `pricing_analytics` API surfaces these as `pricing_category: AI_BOT`. This policy is separate from the October service-message change and only applies if Meta classifies the business itself as an AI Provider. If you only send transactional, marketing, or authentication traffic, it does not change anything for you. Throughput and High-Volume Sending[](https://dualhook.com/docs/pricing#throughput-and-high-volume-sending) ----------------------------------------------------------------------------------------------------------- Throughput is primarily controlled by Meta account quality, [messaging limits](https://dualhook.com/docs/capacity-quality-limits) , and number status. Upgrading between tiers increases Dualhook connection capacity and support level. It does **not** directly purchase higher Meta message throughput. Example Cost Breakdowns[](https://dualhook.com/docs/pricing#example-cost-breakdowns) ------------------------------------------------------------------------------------- **Team plan + high message volume:** * Dualhook: $25/month (Team plan) * Meta: separate usage fees for delivered WhatsApp messages **Agency with many connections:** * Dualhook: $45/month (Agency plan, 10 connections) * Meta: separate usage fees across the connected numbers **Enterprise with 25+ connections:** * Dualhook: $99/month for 25 connections, then $3.50 per additional connection * Meta: separate usage fees across the connected numbers FAQ[](https://dualhook.com/docs/pricing#faq) --------------------------------------------- **Is there a free trial?** Yes. All plans include a 14-day free trial. A payment method is required at sign-up, but you are not charged until the trial ends. **Do you charge for inbound or outbound WhatsApp messages?** No. Message usage is billed by Meta. **If I upgrade my Dualhook plan, will my Meta message pricing change?** No. Meta message pricing is separate. **Can I change plans anytime?** Yes. Upgrades are immediate; downgrades apply at period end. Related ------- * [WhatsApp Pricing Changes 2026](https://dualhook.com/docs/whatsapp-pricing-changes-2026) Meta's 2026 WhatsApp pricing changes: Meta Business Agent billing, 1,000 free monthly service messages per number, chargeable service and in-window utility messages, and October rate cards. * [About Dualhook](https://dualhook.com/docs/about) What Dualhook is, where it fits in the WhatsApp ecosystem, and how it works. * [Quick Start](https://dualhook.com/docs/getting-started) Set up Dualhook and connect your first WhatsApp Business Account in minutes. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Webhook Override: Route Meta Webhooks Directly to Your Server | Dualhook Browse Docs WhatsApp Webhook Override ========================= How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. Looking for the high-level overview first? Start with [WhatsApp Webhook](https://dualhook.com/whatsapp-webhook) and then return here for the implementation details. What Webhook Override Is[](https://dualhook.com/docs/webhook-override#what-webhook-override-is) ------------------------------------------------------------------------------------------------ Webhook Override is Meta's alternate-callback routing for WhatsApp webhook fields. Instead of sending supported webhook fields to the default callback URL on the Meta app, Meta can send them directly to an alternate callback URL configured for a WhatsApp Business Account (WABA) or for a specific business phone number. Dualhook uses this so customer message-path traffic goes from Meta to your endpoint directly. Dualhook is not a message proxy in that flow. In a healthy Dualhook connection with override active: * Meta sends supported customer-path fields to your configured endpoint. * Dualhook does not receive, inspect, store, forward, or replay those payloads. * Dualhook still receives non-overridable management fields on its app-level callback so it can maintain dashboard state, account alerts, and operational metadata. This is the core privacy boundary: message content, media metadata, chat history, app echoes, and contact-sync payloads do not pass through Dualhook when override is active. Routing Priority[](https://dualhook.com/docs/webhook-override#routing-priority) -------------------------------------------------------------------------------- For a supported webhook field, Meta resolves the destination in this order: 1. Phone-number-level alternate callback, if configured. 2. WABA-level alternate callback, if configured. 3. The default app callback URL, if no alternate callback exists. Dualhook configures WABA-level routing. Meta supports a phone-number-level override with higher priority, but Dualhook does not create or manage that override. For webhook fields that do not support override, Meta always sends the webhook to the default app callback URL. Fields That Can Be Overridden[](https://dualhook.com/docs/webhook-override#fields-that-can-be-overridden) ---------------------------------------------------------------------------------------------------------- Meta's override support applies only to specific webhook field types: | Field | Typical path in Dualhook | | --- | --- | | `messages` | Your endpoint | | `message_echoes` | Your endpoint | | `calls` | Your endpoint when calling webhooks are used | | `consumer_profile` | Your endpoint if subscribed | | `messaging_handovers` | Your endpoint if subscribed | | `group_lifecycle_update` | Your endpoint if subscribed | | `group_participants_update` | Your endpoint if subscribed | | `group_settings_update` | Your endpoint if subscribed | | `group_status_update` | Your endpoint if subscribed | | `smb_message_echoes` | Your endpoint | | `smb_app_state_sync` | Your endpoint | | `history` | Your endpoint | | `account_settings_update` | Your endpoint if subscribed | The important operational point is that supported fields are not forwarded by Dualhook. Meta chooses the callback target and sends the webhook there directly. What Stays on Dualhook[](https://dualhook.com/docs/webhook-override#what-stays-on-dualhook) -------------------------------------------------------------------------------------------- Fields that do not support override stay on the app-level callback. Dualhook handles these as operational metadata, not message content. Examples include: * [`message_template_status_update`](https://dualhook.com/docs/webhook-message-template-status-update) * [`message_template_quality_update`](https://dualhook.com/docs/webhook-message-template-quality-update) * [`message_template_components_update`](https://dualhook.com/docs/webhook-message-template-components-update) * [`template_category_update`](https://dualhook.com/docs/webhook-template-category-update) * [`account_update`](https://dualhook.com/docs/webhook-account-update) * [`account_review_update`](https://dualhook.com/docs/webhook-account-review-update) * [`account_alerts`](https://dualhook.com/docs/webhook-account-alerts) * [`business_capability_update`](https://dualhook.com/docs/webhook-business-capability-update) * [`security`](https://dualhook.com/docs/webhook-security) Dualhook may forward normalized management notifications to your configured endpoint and logs delivery metadata for those operational events. It does not store customer message bodies or media. What Dualhook Configures[](https://dualhook.com/docs/webhook-override#what-dualhook-configures) ------------------------------------------------------------------------------------------------ During onboarding, Dualhook collects your webhook endpoint URL and verify token before the Meta Embedded Signup popup opens. After signup completes, Dualhook subscribes the app with that WABA-level override. Meta verifies the endpoint before accepting the configuration. The verify token is used by Meta's GET challenge when it verifies your endpoint. Dualhook stores it only for configuration and retry purposes. Every Dualhook connection under the same WABA must therefore use the same webhook URL and verify token. Changing that configuration updates the WABA and its sibling connections together. Coexistence Syncs[](https://dualhook.com/docs/webhook-override#coexistence-syncs) ---------------------------------------------------------------------------------- For Coexistence connections, Dualhook triggers Meta's one-time `history` and `smb_app_state_sync` sync requests after override is accepted. Those trigger calls are not the payload delivery path. If the business approved history or contact sharing, Meta sends the resulting `history` and `smb_app_state_sync` webhooks directly to your endpoint through the active override. A successful trigger only means Meta accepted the request. It does not prove that the business shared history, that Meta generated every chunk, or that your endpoint received and parsed every webhook. Debugging Routing[](https://dualhook.com/docs/webhook-override#debugging-routing) ---------------------------------------------------------------------------------- The connection **Debug** tab in Dualhook checks the routing signals Meta exposes: * WABA-level webhook override from subscribed apps, * app-level callback context. Dualhook reports WABA routing as the connection's routing mode. If override is active, supported fields should be delivered to your endpoint and should not appear in Dualhook's management-event logs. If override is not active yet, supported fields can fall back to Dualhook's app-level callback. Dualhook's app callback defensively acknowledges those fallback payloads without processing or storing them. That state usually means setup is incomplete, the endpoint failed verification, or billing paused the connection. For the event categories Dualhook does handle, see [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) . Related ------- * [Quick Start](https://dualhook.com/docs/getting-started) Set up Dualhook and connect your first WhatsApp Business Account in minutes. * [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) How Coexistence mode works: using WhatsApp Business App and Cloud API on the same number. * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) Management webhook events: template status, quality updates, account alerts, and forwarding. * [WhatsApp API Architecture & Security](https://dualhook.com/docs/architecture-security) Cloud API architecture, encryption, data residency, GDPR, and Dualhook's direct-routing security model. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Billing Enforcement | Dualhook Browse Docs Billing Enforcement =================== What happens to Dualhook connections when a subscription ends or a plan limit is exceeded. Looking for the high-level overview first? Start with [Pricing](https://dualhook.com/pricing) and then return here for the implementation details. Overview[](https://dualhook.com/docs/billing-enforcement#overview) ------------------------------------------------------------------- Dualhook uses one connection-routing state for billing entitlement, plan limits, administrator holds, Meta enforcement, and terminal cleanup. Only active connections and connections in plan-limit grace can use the outbound runtime. A blocked connection still consumes a plan slot but cannot route API requests. A terminating connection no longer consumes a slot. Reversible billing and plan-limit blocks are connection pauses, not destructive disconnects. Subscription Status[](https://dualhook.com/docs/billing-enforcement#subscription-status) ----------------------------------------------------------------------------------------- Dualhook keeps connections usable while a subscription is: * active * trialing * past due while Stripe is still retrying payment * scheduled to cancel at the end of its current billing period Dualhook blocks routing when Stripe reports a non-entitled status such as unpaid, paused, or incomplete and expired. A failed invoice does not start the five-day plan-limit grace period: Stripe's own retry and dunning period applies instead. Local outbound access is blocked first. Dualhook then attempts to clear the WABA-level [Webhook Override](https://dualhook.com/docs/webhook-override) without delaying the local block. If another current connection shares that WABA, Dualhook leaves the WABA callback active because clearing it would also interrupt the sibling connection. The blocked connection still cannot use Dualhook's outbound API. Plan-Limit Grace[](https://dualhook.com/docs/billing-enforcement#plan-limit-grace) ----------------------------------------------------------------------------------- If a downgrade or other plan change leaves more connections than the plan permits, the deterministic over-limit connections receive five days of grace. They remain usable during that grace period. After grace expires, those connections become blocked. Upgrading or freeing enough slots releases only the plan-limit hold; unrelated administrator, billing, or Meta holds remain in effect. What Is Preserved[](https://dualhook.com/docs/billing-enforcement#what-is-preserved) ------------------------------------------------------------------------------------- Pausing a connection does **not** wipe its setup. Dualhook keeps: * Connection name and organization link * WABA ID and phone number ID * Webhook URL and verify token * Access token and related configuration * Existing `dh_live_...` runtime keys That means you do **not** need to redo [Embedded Signup](https://dualhook.com/docs/embedded-signup) just because billing paused a connection. How Reactivation Works[](https://dualhook.com/docs/billing-enforcement#how-reactivation-works) ----------------------------------------------------------------------------------------------- When you subscribe again or move back to a plan with enough capacity, Dualhook attempts to restore the paused connection automatically. If reactivation succeeds: * The stored WABA-level Meta webhook override is restored and read back for verification. * The relevant billing or plan-limit hold is released. * The existing runtime key becomes usable again. * Unrelated holds remain in effect. Dualhook keeps the connection blocked if Meta rejects restoration or verification. Payment recovery alone does not claim that inbound routing is healthy. Final Cancellation[](https://dualhook.com/docs/billing-enforcement#final-cancellation) --------------------------------------------------------------------------------------- Scheduling a cancellation does not immediately remove connections. They remain usable through the paid period. When Stripe sends its final subscription-deleted event, Dualhook starts terminal cleanup. It immediately denies local access and revokes runtime keys, clears the connection's remote override where possible, conditionally unsubscribes the app only when no sibling connection still needs the WABA, erases stored credentials, and removes the connection row. Retryable cleanup progress remains in a credential-free audit record. Because Meta does not provide a partner-side API to sever every Embedded Signup relationship, customers are also instructed to remove Dualhook under Meta Business Settings → Integrations → Connected apps. Related ------- * [Pricing](https://dualhook.com/docs/pricing) Dualhook subscription plans, what is included, and how pricing relates to Meta messaging fees. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Troubleshooting](https://dualhook.com/docs/troubleshooting) Common issues and solutions for Dualhook connections, webhooks, and health monitoring. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Pricing Changes 2026: Aug 1 & Oct 1 | Dualhook Browse Docs WhatsApp Pricing Changes 2026 ============================= Meta's 2026 WhatsApp pricing changes: Meta Business Agent billing, 1,000 free monthly service messages per number, chargeable service and in-window utility messages, and October rate cards. Looking for the high-level overview first? Start with [Pricing](https://dualhook.com/pricing) and then return here for the implementation details. Every dated WhatsApp Business Platform pricing change in the second half of 2026, sourced to Meta's own documentation: what changes, when, what it costs, and what you have to change in your code. _Last verified on 4 September 2026 against Meta's pricing documentation and its 1 September 2026 pricing and billing notice. Re-check the official rate card for your billing currency before committing a budget._ Direct Answer[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#direct-answer) --------------------------------------------------------------------------------------- Three things change on the WhatsApp Business Platform in the second half of 2026, and one structural change has already happened. On **1 July 2026**, Meta split non-template messages into two categories: **service messages** (powered by a person or a third-party AI) and **Meta Business Agent messages** (powered by Meta's own agent platform). Before that date, every non-template reply was a service message. On **1 August 2026**, Meta began charging for Meta Business Agent messages on a per-token basis, at one global rate of **2.00 USD per one million tokens**. On **1 October 2026**, Meta will begin charging for **service messages** and for **utility templates sent inside an open 24-hour customer service window**. Both are free today. Each phone number receives **1,000 free service messages per month**; only its 1,001st service message onward is charged. The allowance refreshes monthly and does not roll over. Paid service messages use the utility and authentication list rate for the recipient's market and have **no volume tiers**. Also on **1 October 2026**, Meta will update marketing rates in six markets and utility and authentication rates in twelve markets. Nine markets move out of regional "Rest Of" pricing onto standalone utility and authentication rate cards, and receive new authentication-international rates. The October rate cards are now published. Use the card for your WABA's billing currency and the recipient's country calling code; do not use the examples on this page as a substitute for the official card. The Four Changes, With Dates[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#the-four-changes-with-dates) -------------------------------------------------------------------------------------------------------------------- | Date | What changes | Status as of 4 Sep 2026 | | --- | --- | --- | | 1 July 2026 | Non-template messages split into two categories: service, and Meta Business Agent. No price change on this date. | In effect | | 1 August 2026 | Meta Business Agent messages charged per token, one global rate | In effect since 12pm PT | | 8 September 2026 | Credit and debit cards become available for Meta Business Agent billing | Announced | | 15 September 2026 (target) | Meta Business Agent token-consumption and cost APIs become available | Target date | | 1 October 2026 | Service messages charged per message after 1,000 free service messages per phone number each month | Scheduled, rates published | | 1 October 2026 | Utility templates sent inside an open customer service window charged per message | Scheduled, rates published | | 1 October 2026 | Six marketing-market and twelve utility/authentication-market rate updates; nine utility/authentication markets move out of "Rest Of" regions | Scheduled, rates published | | October 2026 (date TBC) | Marketing Messages API max-price Open Beta becomes available to all businesses | Announced, exact date TBC | Why the 1 July 2026 Category Split Matters[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#why-the-1-july-2026-category-split-matters) ------------------------------------------------------------------------------------------------------------------------------------------------- Most coverage of these changes starts with the money. It is easier to follow if you start with the taxonomy, because the pricing follows the category. Meta's own wording, from its non-template messages pricing page: > Until July 1, 2026: All non-template message responses to users were service messages (`category: service`). From 1 July 2026 there are two categories of non-template message: * **Meta Business Agent messages** — powered by the Meta Business Agent Platform. A new category. * **Service messages** — powered by a person, such as a customer service representative, or by a third-party AI solution. Any non-template message that is not powered by Meta Business Agent is a service message. Two structural constraints follow, and both are worth internalising before reading any rate table: 1. **Non-template messages can only be sent inside an open 24-hour customer service window**, which opens and resets with each user message. A service message is by definition a reply. You cannot use one to reach out. Any claim that service messages will be charged "regardless of the 24-hour window" is a category error: there is no such thing as a service message outside the window. See [Service Messages](https://dualhook.com/docs/service-messages) . 2. **Only one charge ever applies per message.** Meta states this explicitly. A non-template message is charged either as a Meta Business Agent message or as a service message, never both. Meta also spells out a corollary that is easy to miss: a non-template message containing promotional content does not additionally incur a marketing template charge. Change 1: Meta Business Agent Messages, Per Token[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#change-1-meta-business-agent-messages-per-token) ------------------------------------------------------------------------------------------------------------------------------------------------------------- ### The rate[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#the-rate) Meta publishes a single figure, and it is global rather than per-market: * **Meter** — per message, based on one per-token charge covering both AI agent processing (token consumption) and message delivery. * **Rate** — one global rate of **2.00 USD per 1 million tokens**. * **Cache tokens** — Meta does **not** charge for cache tokens. The rate and typical per-message estimate already exclude them. * **Typical consumption** — Meta states one message typically consumes **20,000 to 25,000 tokens**, roughly **4 to 5 US cents per message**. * **Cost varies with complexity.** Simple responses consume fewer tokens and cost less; complex responses consume more. Meta's own worked examples: | Type of interaction | Example user message | Messages to the user | Tokens consumed | Estimated cost | | --- | --- | --- | --- | --- | | Simple inquiry | "At what time do you open?" | 4 | ~80,000 (~20,000 per message) | ~16 to 20 cents | | Complex interaction | "I'm stuck on step 3 of this assembly, can you walk me through it?" | 10 | ~250,000 (~25,000 per message) | ~40 to 50 cents | Note carefully that the 4-to-5-cents-per-message figure belongs to **Meta Business Agent messages only**. It is not the service message rate. Several secondary write-ups have carried that number across to service messages, which overstates service message cost by a wide margin in most markets: Meta's own comparison table puts message delivery for Brazil at 0.68 US cents. ### Billing setup and payment methods[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#billing-setup-and-payment-methods) This is the detail most likely to break a production rollout, and Meta states it as a warning note rather than burying it: > As of August 1, 2026 at 12pm PT — Meta will begin to charge for Meta Business Agent messages delivered via our API. Meta Business Agent messages will not be delivered if a credit line is _not_ attached to your Business Agent account. Not "charged later." Not delivered. If you switch Meta Business Agent on without a credit line attached, your replies do not reach the user. On who this applies to and how billing works, Meta is equally direct: > Consistent with industry norms, Meta will charge all businesses for Meta Business Agent, including those that access the WhatsApp Business Platform today supported by a Solution Provider(s). Billing for Meta Business Agent will be via monthly invoicing — via a credit line with Meta — with support for credit cards as a payment method in September (target). Two consequences worth planning around: * **Being on a Solution Partner does not exempt you.** Meta charges the business directly for Meta Business Agent even where a Solution Partner handles the rest of the WhatsApp billing relationship. * **Getting a credit line is not instant.** If you already have one for ads or messaging, you attach your Business Agent account to it in Billing Hub. If you do not, Meta's wording is that you apply for one in Billing Hub and it is "subject to review and approval by Meta." Start that before you plan a launch date, not after. Meta's 1 September notice says credit and debit cards become available as a payment method on **8 September 2026**. Until then, the credit-line requirement remains the documented delivery blocker. Meta also targets **15 September 2026** for APIs that report token consumption and costs in near real time. ### Currency[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#currency) As of 1 July 2026, Meta lists 16 currencies enabled on the WhatsApp Business Platform, and a Meta Business Agent account can be created in one of them. If the account is not in USD, Meta invoices in that currency, converting from the global USD rate using the exchange rate on the day each message was delivered. Businesses whose Sold-To country in Billing Hub is India or Brazil can only choose their respective local currency. One wrinkle in Meta's own table: the Brazil row lists BRL, but the footnote against it reads "not yet available." So "16 enabled" and "16 selectable today" are not the same set. If you are billing in Brazil, confirm the current position in Billing Hub before assuming you can create the account. This matters because the Meta Business Agent rate is published as a single global USD figure. Everyone else's invoice is a conversion of it, so a currency move shows up in your costs even when Meta's rate has not changed. Change 2: Service Messages Become Chargeable[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#change-2-service-messages-become-chargeable) ---------------------------------------------------------------------------------------------------------------------------------------------------- Meta's wording: > Effective October 1, 2026 - Meta will charge on a per-message basis for all **service messages**, consistent with how Meta charges for template messages. These messages have not been charged since November 1, 2024. The pricing detail, from the same page: * **Charge for** — delivered messages, not sent messages. * **Meter** — per message. * **Rate** — by market, service message rates will be **the same as the rates for utility and authentication messages** in that market. * **Free monthly tier** — each phone number gets **1,000 free service messages per month**. The allowance refreshes monthly, does not roll over, and only the 1,001st service message onward is charged. * **Volume tiers** — **none.** Meta states it will not offer volume tiers for service messages, while continuing to offer them for utility and authentication messages. * **Rate publication** — the rates taking effect on 1 October 2026, including service-message rates, are now published on Meta's pricing page. The free tier and volume tiers solve different problems. A number sending 100 service messages in a month pays nothing for them; a number sending 100,000 pays the same market list rate for each billable service message after the first 1,000. Unlike utility and authentication templates, service messages do not get progressively cheaper at higher volumes. Change 3: In-Window Utility Templates Become Chargeable[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#change-3-in-window-utility-templates-become-chargeable) -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Meta's wording: > Effective October 1, 2026 - Meta will charge on a per-message basis for **utility messages sent in response to users** (within an open 24-hour customer service window). These messages have not been charged since July 1, 2025. [Utility templates](https://dualhook.com/docs/utility-messages) sent **outside** an open window have always been charged and continue to be. What ends on 1 October 2026 is the in-window exemption in place since 1 July 2025. Practical read: the order confirmation you send while the customer is still chatting with you has been free for fifteen months. From October it costs the same as the one you send the next day. Change 4: October Rate Card Updates[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#change-4-october-rate-card-updates) ---------------------------------------------------------------------------------------------------------------------------------- Separately from the category changes, Meta will update marketing rates across six markets and utility and authentication rates across twelve markets on 1 October 2026. Two of the marketing markets and nine of the utility/authentication markets move out of their respective "Rest Of" pricing regions. The nine newly standalone utility and authentication markets announced in May are: | Markets | Change to utility and authentication rates | Authentication-international | | --- | --- | --- | | Bangladesh, Iraq, Nepal, Sri Lanka | Lower | New authentication-international rate, higher than the current regional authentication rate | | Kazakhstan, Kuwait, Morocco, Oman, Ukraine | Higher | New authentication-international rate, higher than the current regional authentication rate | Meta's 1 September notice confirms these nine moves and says the October rates are now published. It also confirms new authentication-international rates for all nine newly standalone markets. Because service message rates will match utility and authentication rates, a market moving onto a standalone rate card on 1 October 2026 changes that market's service message rate on the same day. The two changes compound in those nine markets. The Complete Billing Matrix[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#the-complete-billing-matrix) ------------------------------------------------------------------------------------------------------------------- This consolidates Meta's category table, its pricing page, and the free monthly tier announced on 1 September 2026. | Message type | Sent when | Before 1 Aug 2026 | 1 Aug 2026 | From 1 Oct 2026 (scheduled) | | --- | --- | --- | --- | --- | | Marketing template | Any time | Charged | Charged | Charged | | Utility template, outside customer service window | Any time | Charged | Charged | Charged | | Authentication template | Any time | Charged | Charged | Charged | | Utility template, inside customer service window | In open CSW | Free (since 1 Jul 2025) | Free | **Charged, per message** | | Service message (person or third-party AI) | In open CSW | Free (since 1 Nov 2024) | Free | First 1,000 per phone number per month free, then **charged per message with no volume tiers** | | Meta Business Agent message | In open CSW | Free (test window, 1 to 31 Jul 2026) | **Charged, per token** | Charged, per token | | Any inbound message from a user | Any time | Not charged | Not charged | Not charged | | Marketing, utility, authentication or service message inside 72-hour free entry point window | In open FEP | Free | Free | Free | | Meta Business Agent message inside 72-hour free entry point window | In open FEP | Free | **Charged for token usage** | Charged for token usage | That last row is the one both the mainstream write-ups and the AI summaries keep getting wrong. The 72-Hour Free Entry Point Exception[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#the-72-hour-free-entry-point-exception) ----------------------------------------------------------------------------------------------------------------------------------------- A free entry point window opens when a WhatsApp user messages you through a Click to WhatsApp ad or a Facebook Page call-to-action button, from a device running Meta's Android or iOS app (desktop and web are not supported), **and** you respond within 24 hours. The FEP window then runs for 72 hours from the time you responded. Marketing, utility, authentication, and service message delivery is free while it is open; Meta Business Agent token usage is the exception. Meta's own category table states that marketing, utility, authentication and service messages are all free inside the 72-hour FEP window. It also states, in the same table, that Meta Business Agent messages are **not**. Meta spells this out: > The 72-hour free entry point window is unchanged for message delivery. Messages sent within the 72-hour free entry point window, from Click to WhatsApp Ads or Facebook call-to-action buttons, remain free for message delivery. Meta Business Agent messages are still charged for token usage as of August 1, 2026. The logic is consistent: the FEP waiver covers **message delivery**. Meta Business Agent's charge is a token charge bundling AI processing and delivery, so the waiver does not neutralise it. If your acquisition funnel leans on Click to WhatsApp ads and you route those conversations to Meta Business Agent, the FEP window no longer makes those replies free. It still does if a human or your own AI answers them. Note also that the customer service window and the FEP window are independent. If the customer service window closes, you can only send template messages, whether or not the FEP window is still open. Meta's Worked Example: One Charge Becomes Up to Five[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#metas-worked-example-one-charge-becomes-up-to-five) ------------------------------------------------------------------------------------------------------------------------------------------------------------------- Meta publishes a single conversation traced across the three pricing states. It is the clearest illustration available and it comes from the primary source. The scenario: a business sends a cart abandonment reminder, the user asks two questions answered by Meta Business Agent, the agent hands off to a human rep who answers one more question, and the business sends an order confirmation. All within one hour. | Sender | Message | Type | Category | Charge as of 1 Aug 2026 | Charge as of 1 Oct 2026 | | --- | --- | --- | --- | --- | --- | | Business | Cart abandonment reminder | Template | Marketing | 1 marketing charge | 1 marketing charge | | User | "Do you have this in a Small?" | | | Opens 24-hour CSW | | | Business | Agentic response | Non-template | Meta Business Agent | 1 MBA charge | 1 MBA charge | | User | "Do you have this in white?" | | | | | | Business | Agentic response | Non-template | Meta Business Agent | 1 MBA charge | 1 MBA charge | | User | "I'm ready to check out, but my card is expired" | | | | | | Business | Rep responds | Non-template | Service | No charge | 0 charges while the number is inside its monthly free tier, otherwise 1 service charge | | Business | Order confirmation | Template | Utility | No charge | 1 utility charge | | | | | **Total** | **3 charges** | **4 or 5 charges** | As of 1 July 2026 the same conversation carried exactly one charge. It moves to three from August, then four or five from October depending on whether the phone number has exhausted its 1,000 free monthly service messages. The behavioural consequence is direct once a phone number has exhausted its free monthly service tier. An agent or bot that splits one answer across four short service messages can produce four charges where one would have done. What This Will Actually Cost You[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#what-this-will-actually-cost-you) ----------------------------------------------------------------------------------------------------------------------------- Meta has published the October rates. The remaining work is to select the correct billing-currency card, map each recipient country code to its market, and apply the free service-message tier per phone number. The formula: Added monthly cost from 1 October 2026 = (service messages Meta marks billable after the 1,000-message free tier x service rate for each recipient market) + (in-window utility templates delivered per month x utility rate for that market) Both inputs are things you can measure today. Neither is something you should estimate. **An illustration using a rate Meta has published.** In its own cost comparison, Meta uses 0.68 US cents as the message delivery rate for utility and authentication messages to users in Brazil. Assuming one phone number, all service messages go to Brazil, none fall inside a free entry point window, and that rate is the applicable October service rate: * 10,000 service messages: (10,000 - 1,000 free) x 0.0068 USD = **61.20 USD per month** * 50,000 service messages: (50,000 - 1,000 free) x 0.0068 USD = **333.20 USD per month** Do not carry that figure to other markets; use Meta's current rate card for each recipient market. **Meta's service versus Meta Business Agent comparison**, scaled to 1,000 AI-powered replies to users in Brazil from Meta's earlier 10,000-message example. The table predates the free service-message tier, so its service delivery line is the cost after the free allowance has been exhausted. Third-party AI estimates are Meta's own, based on what it describes as publicly available information and benchmarks: | | Service, lower complexity AI | Service, higher complexity AI | Meta Business Agent | | --- | --- | --- | --- | | AI agent cost | ~2 cents per message | ~9 cents per message | ~4 to 5 cents per message | | Message delivery | 0.68 cents per message | 0.68 cents per message | included in token charge | | Total per message | ~2 to 3 cents | ~9 to 10 cents | ~4 to 5 cents | | Total for 1,000 messages | ~27 USD | ~97 USD | ~40 to 50 USD | Read that table with its provenance in mind. It is Meta's comparison of Meta's product against third-party alternatives, and the third-party AI cost assumptions are Meta's own. It is not an independent benchmark of what a third-party AI stack costs you. It also does not mean the first 1,000 service messages now incur the displayed delivery charge. ### How to choose the correct market rate[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#how-to-choose-the-correct-market-rate) Meta charges by the **recipient's country calling code**, not by your company's headquarters, incorporation country, or server location. Look up each recipient market in Meta's current country mapping: some countries have standalone rate cards, while others share a regional "Rest of" rate. For an international customer base, model traffic by recipient market instead of applying one headquarters-based rate. A weighted forecast across your largest destination countries is more accurate than a single global average, especially because the nine October market changes move in different directions. Meta publishes rate cards in multiple billing currencies, including USD and EUR. The effective date and monthly volume-tier reset use the WhatsApp Business Account timezone. For Developers: Detecting the Change in Your Own Data[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#for-developers-detecting-the-change-in-your-own-data) ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- This is the part of the change with an implementation cost, and the part almost no coverage addresses. ### Today: free service messages[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#today-free-service-messages) The `pricing` object in a status webhook for a non-template message inside the customer service window currently looks like this: "pricing": { "billable": false, "pricing_model": "PMP", "type": "free_customer_service", "category": "service" } An in-window utility template currently looks like this: "pricing": { "billable": false, "pricing_model": "PMP", "type": "free_customer_service", "category": "utility" } ### From 1 October 2026: billable service messages[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#from-1-october-2026-billable-service-messages) Meta documents the post-change shape for a billable service message: "pricing": { "billable": true, "pricing_model": "PMP", "type": "regular", "category": "service" } So the diff to watch for on a service message is `billable` flipping from `false` to `true` and `type` flipping from `free_customer_service` to `regular`. The `category` stays `service`. That is the billable shape. Meta's 1 September notice does not document the status-webhook shape for service messages covered by the new monthly free tier, so do not assume every service delivery flips to `regular` on 1 October. **Do not key your cost logic on `category` alone.** If your billing, reporting, or cost-attribution code currently treats `category: "service"` as a synonym for "free", it will silently under-report from 1 October 2026. Key on `billable` and `type`. Meta has not published the equivalent post-change payload for in-window utility templates. The reasonable expectation is `type` moving from `free_customer_service` to `regular` with `category` remaining `utility`, but that is an inference, not a documented fact. Treat it as an assumption to verify rather than a specification to build against. Because Dualhook routes message-path webhooks directly from Meta to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) , the raw `pricing` object arrives at your server unmodified. That is the data your October cost model should be built on — not a provider's summarised view of it. ### Counting service messages before October[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#counting-service-messages-before-october) You can size your exposure today with the Pricing Analytics API. Query with `pricing_category` set to `SERVICE`: { "start": "", "end": "", "phone_number": "", "country": "", "pricing_type": "REGULAR", "pricing_category": "SERVICE", "volume": "", "cost": "" } Use the returned `volume` to model each phone number and recipient market, then account for the first 1,000 service messages per phone number being free. Rates differ by market, and Meta has not yet documented how a number's free allowance is allocated when it serves multiple markets. Dualhook surfaces the same per-category volume breakdown on each connection's Usage tab. ### Meta Business Agent analytics[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#meta-business-agent-analytics) Meta targets **15 September 2026** for new APIs that expose Meta Business Agent token consumption and costs across a specified timeframe in near real time. Treat that as a target, not a shipped interface, until the API documentation is live. ### Volume tier webhooks[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#volume-tier-webhooks) Since 1 October 2025, an [`account_update`](https://dualhook.com/docs/webhook-account-update) webhook with `event` set to `VOLUME_BASED_PRICING_TIER_UPDATE` fires when a WABA reaches a new volume tier in a market in a given month. Tiers apply to utility and authentication only, aggregate at business portfolio level across all WABAs, and reset monthly at 00:00 in the WABA's timezone. Service messages will never trigger this, because they have no tiers. What Coexistence Users Are Not Charged For[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#what-coexistence-users-are-not-charged-for) ------------------------------------------------------------------------------------------------------------------------------------------------- If your number runs [Coexistence](https://dualhook.com/docs/coexistence) , the October change does not reach messages your team sends from the WhatsApp Business app. Meta states this directly in its Coexistence onboarding documentation: > After a business customer has been onboarded to Cloud API, messages sent by the business via the WhatsApp Business app will continue to be free, but messages sent via Cloud API will be subject to Cloud API pricing. Meta is equally explicit that app-sent messages sit outside the window machinery entirely: > The 24-hour customer service window restriction applies to messages sent via Cloud API. Messages sent from the WhatsApp Business app are not subject to the customer service window and do not create, extend or affect Cloud API conversation windows or Cloud API pricing. The practical read for a Coexistence deployment: a human replying from the phone is free and always has been, and stays free after 1 October 2026. The same human replying through Cloud API counts as a service message from that date; it uses the number's free monthly allowance, then becomes chargeable. App-sent messages still reach your endpoint as [`smb_message_echoes`](https://dualhook.com/docs/webhook-smb-message-echoes) webhooks, so you can see them, but seeing them is not sending them and carries no charge. This is worth stating plainly because it is widely reported as an open question. It is not one — Meta answers it on the Coexistence page rather than on the pricing pages, which is why it gets missed. Three Remaining Questions[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#three-remaining-questions) --------------------------------------------------------------------------------------------------------------- Stating these openly is more useful than filling the gaps with confident guesses, and every one is a real question a team will hit while planning. **1\. Do in-window utility templates start counting toward volume tiers once they are charged?** Meta's rule today is that only charged messages accrue toward volume tiers, and it explicitly excludes utility templates delivered inside a customer service window and inside a free entry point window. From 1 October 2026, in-window utility templates become charged. Whether they then begin accruing is not addressed in Meta's documentation as of 4 September 2026. If they do, part of the October increase is offset by faster tier progression on utility. **This is an inference and should not be modelled as fact.** Ask Meta or your Solution Provider to confirm. **2\. How is the free service-message tier represented and allocated?** The 1 September notice establishes the 1,000-message allowance per phone number, monthly refresh, and no rollover. It does not specify the status-webhook `type` used for a free-tier delivery, whether free entry point deliveries consume the allowance, or how the allowance is allocated when one number serves markets with different rates. Do not build billing logic around an assumed answer. **3\. Will the October rates hold?** Meta's pricing calendar permits rate updates on the first day of each quarter only: 1 January, 1 April, 1 July, 1 October. Rate card updates carry a minimum of one month's advance notice, pricing model add-ons three months, and pricing model changes six months. The next opportunity to change these rates after October is therefore 1 January 2027, with notice. The EU and EEA AI Providers Wrinkle[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#the-eu-and-eea-ai-providers-wrinkle) ----------------------------------------------------------------------------------------------------------------------------------- There is a fourth pricing track running alongside the three above, specific to a class of business rather than a class of message. It matters disproportionately in Europe. In October 2025 Meta updated its WhatsApp Business Solution Terms to restrict "AI Providers," defined as providers and developers of AI or machine learning technologies where such technology is the primary rather than incidental or ancillary functionality being made available. The terms took effect on 15 October 2025 for new users of the platform and 15 January 2026 for existing ones, with a carve-out permitting such technologies to be made available to WhatsApp users with European Economic Area or Brazil country codes. Meta then introduced a pricing policy for AI Providers. Per Meta's changelog: effective 16 February 2026, in countries where Meta is legally required to support AI Providers, Meta would charge AI Providers for non-template messages sent to WhatsApp users. Meta subsequently updated that policy: **effective 13 May 2026, Meta will no longer charge AI Providers for non-template messages delivered to users in EU and EEA markets.** Charges for non-template messages delivered to users in Brazil remain in effect. The backdrop is a European Commission antitrust proceeding, and it has already moved past the stage most coverage stops at. The Commission issued a Statement of Objections on 9 February 2026, saying Meta's policy change appeared at first sight to breach EU competition rules. Then, on **9 June 2026, the Commission imposed interim measures** ordering Meta to restore free access to the WhatsApp Business API for third-party general-purpose AI assistants, on the terms that applied before 15 October 2025, and to keep that access in place until the investigation concludes. Interim measures are rare — this was only the second such order under Regulation 1/2003 since Broadcom in 2019. So the EU position is not merely that Meta chose to stop charging AI Providers in EU and EEA markets from 13 May 2026; a binding order now requires free access there while the case runs. The underlying investigation is still open, so the final outcome can change again. Two practical notes. First, this track only applies if Meta classifies you as an AI Provider, which turns on whether AI is your primary product rather than a feature supporting a business use case. A retailer running an AI order-tracking bot is not an AI Provider; a general-purpose assistant distributed over WhatsApp is. Meta reserves that determination to itself. Second, if there is any chance the classification applies to you, this is a question for counsel rather than for a pricing page. Nothing here is legal advice. What to Do Before 1 October 2026[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#what-to-do-before-1-october-2026) ----------------------------------------------------------------------------------------------------------------------------- An ordered list. The first item is the only one that is genuinely urgent. 1. **Measure service-message volume per phone number and market.** Use the Pricing Analytics API with `pricing_category: "SERVICE"`, broken out by country. Model the 1,000-message free tier per phone number before applying market rates. 2. **Measure your in-window utility template volume separately.** Same method, different category. Distinct line item, distinct rate. 3. **Audit your reply granularity.** Count average outbound messages per resolved conversation. After a number uses its monthly allowance, a bot or agent that splits one answer across several bubbles creates several service-message charges. One reply, one message, is then a cost control rather than a style preference. 4. **Fix your cost-attribution code before the flag flips.** If anything in your stack treats `category: "service"` as free, it will under-report from 1 October. Key on `billable` and `type` instead. 5. **Check template categorisation.** Sending a transactional confirmation as a marketing template has always cost more; from October, misclassification errors spread across more of your traffic. See [template category updates](https://dualhook.com/docs/webhook-template-category-update) . 6. **Push volume where tiers still exist.** Utility and authentication templates get volume tiers; service messages never will. Where a use case can legitimately be served by a utility template rather than a free-form reply, the tiered rate may end up cheaper at scale. "Legitimately" is doing work in that sentence — categorisation rules still apply. 7. **Download the published October rate card for your WABA currency.** Replace any July-rate assumptions and repeat the calculation per recipient market. 8. **If you plan to use Meta Business Agent, confirm billing first.** Meta's earlier documentation requires an attached credit line for delivery; its 1 September notice says card payments become available on 8 September. Verify that the payment method is active before launch. 9. **Separate Meta's charges from your platform fees in your model.** They move independently and for different reasons. Where Dualhook Fits[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#where-dualhook-fits) --------------------------------------------------------------------------------------------------- Dualhook is a badged Meta Tech Partner, not a Business Solution Provider. Customers keep their own Meta Business assets and their own billing relationship with Meta, and Dualhook charges a flat platform fee. Dualhook does not resell WhatsApp messaging and does not mark up Meta's messaging charges. See [Dualhook pricing](https://dualhook.com/pricing) and the [pricing doc](https://dualhook.com/docs/pricing) . The practical consequence for these changes is narrow and worth stating precisely rather than overselling: the October increase lands on your Meta invoice at Meta's published rates, and Dualhook's fee does not move with your message volume. There is no per-message layer on top. What Dualhook does is operational — connection setup through [Embedded Signup](https://dualhook.com/docs/whatsapp-embedded-signup-guide) , [Webhook Override](https://dualhook.com/docs/webhook-override) so message events reach your own endpoint directly, [template management](https://dualhook.com/docs/templates) , and [account health monitoring](https://dualhook.com/docs/account-health) . **Do not hardcode Meta's rates into billing logic.** They vary by recipient market and category and can change on the first day of any quarter. Pull them from Meta's rate cards and revalidate quarterly. FAQ[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#faq) ------------------------------------------------------------------- **What changed on 1 August 2026?** Meta began charging for Meta Business Agent messages on a per-token basis, at one global rate of 2.00 USD per million tokens, from 12pm PT. A typical message consumes 20,000 to 25,000 tokens, roughly 4 to 5 US cents. Cache tokens are not charged. **What changes on 1 October 2026?** Two things become chargeable that are free today: service messages, meaning free-form replies sent inside the 24-hour customer service window by a person or a third-party AI, and utility templates sent inside that same window. The first 1,000 service messages per phone number remain free each month. Separately, six markets get marketing-rate updates, twelve get utility/authentication-rate updates, and nine of the latter move onto standalone rate cards. **How much will a WhatsApp service message cost?** The first 1,000 per phone number each month are free. After that, the price is the published service rate for the recipient's market, which matches that market's utility and authentication list rate. Service messages do not have volume tiers. **What is the difference between a service message and a Meta Business Agent message?** Both are non-template replies inside an open customer service window. A Meta Business Agent message is powered by Meta's own agent platform and is charged per token from 1 August 2026. A service message is powered by a person or a third-party AI and will be charged per message from 1 October 2026. Any non-template message not powered by Meta Business Agent is a service message. **Can one WhatsApp message be charged twice?** No. Meta states that only one charge applies per message. A non-template message is charged either as a Meta Business Agent message or as a service message, never both. A non-template message containing promotional content does not additionally incur a marketing template charge. **Are messages inside the 72-hour free entry point window still free?** For marketing, utility, authentication and service messages, yes. For Meta Business Agent messages, no. Meta states those are still charged for token usage from 1 August 2026, because the free entry point waiver covers message delivery while the Meta Business Agent charge bundles AI processing with delivery. **How can I pay for Meta Business Agent?** Meta's launch documentation used monthly invoicing through an attached credit line and warned that messages would not be delivered without one. Its 1 September notice says credit and debit cards become available on 8 September 2026. Confirm the active payment method in Billing Hub before sending; being supported by a Solution Provider does not exempt a business from Meta Business Agent charges. **Are inbound WhatsApp messages from customers charged?** No. Messages a user sends to your business are not charged, and they open or reset the 24-hour customer service window. **Do I need to change my code for the October 2026 pricing change?** If you attribute costs from delivery status webhooks, yes. The `pricing` object for a service message changes from `billable: false` with `type: "free_customer_service"` to `billable: true` with `type: "regular"`. The `category` remains `service`, so logic keyed on category alone will silently under-report. **Does switching WhatsApp providers avoid the new charges?** No. These are Meta's charges, applied at Meta's rates based on the recipient's market and message category. What differs between providers is whether they add a markup on top and how much visibility you get into the underlying charge data. **How much notice will Meta give for the next change?** Meta may update pricing only on the first day of a quarter. Its published minimum notice periods are one month for a rate card update, three months for a pricing model add-on, and six months for a pricing model change. Sources[](https://dualhook.com/docs/whatsapp-pricing-changes-2026#sources) --------------------------------------------------------------------------- * Meta, [Pricing on the WhatsApp Business Platform](https://developers.facebook.com/documentation/business-messaging/whatsapp/pricing) * Meta, [Pricing for non-template messages](https://developers.facebook.com/documentation/business-messaging/whatsapp/pricing/non-template-messages) * Meta, “Pricing and billing updates – Updated rates for Oct. 1 '26 and updates for Meta Business Agent,” partner notice dated 1 Sep 2026 * Meta, [WhatsApp Business Platform changelog](https://developers.facebook.com/documentation/business-messaging/whatsapp/changelog) * Meta, [AI Providers pricing policy](https://developers.facebook.com/documentation/business-messaging/whatsapp/pricing/ai-providers) * Meta, [Analytics, including the Pricing Analytics API](https://developers.facebook.com/documentation/business-messaging/whatsapp/analytics) * Meta, [Status messages webhook reference](https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/reference/messages/status) * WhatsApp, [Business Solution Terms](https://www.whatsapp.com/legal/business-solution-terms) * Meta, [Onboarding WhatsApp Business app users](https://developers.facebook.com/documentation/business-messaging/whatsapp/embedded-signup/onboarding-business-app-users) — the Pricing section covering Coexistence app-sent messages * European Commission, [antitrust investigation into Meta's policy on AI providers' access to WhatsApp](https://ec.europa.eu/commission/presscorner/detail/en/ip_25_2896) * European Commission, [interim measures ordering Meta to preserve free WhatsApp access for rival AI assistants, 9 June 2026](https://ec.europa.eu/commission/presscorner/detail/en/ip_26_1276) All checked 4 September 2026. Use Meta as the authority for every rate, date, and field name. Secondary coverage is useful for confirming that a change is being widely reported, not for establishing what the change is. Related ------- * [Pricing](https://dualhook.com/docs/pricing) Dualhook subscription plans, what is included, and how pricing relates to Meta messaging fees. * [Service Messages](https://dualhook.com/docs/service-messages) Free-form messages inside an open customer service window: types, replies, TTL, and October 2026 pricing with 1,000 free messages per number each month. * [Utility Messages](https://dualhook.com/docs/utility-messages) Utility template messaging hub: components and buttons, specialty types, the October 2026 CSW billing change, volume tiers, content rules, and recategorization. * [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) API rate limits, messaging limit tiers, quality rating, and scaling guidance. * [account\_update Webhook](https://dualhook.com/docs/webhook-account-update) Field reference for WhatsApp account\_update webhooks: WABA lifecycle events, restrictions, partner events, pricing tiers, MM API terms, offboarding, and Dualhook handling. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Support, Contact, and Bug Reports | Dualhook Browse Docs Support, Contact, and Bug Reports ================================= How to contact Dualhook support, use the in-app support chat, report bugs, attach screenshots or PDFs, and know when email is the right path. Where to Contact Dualhook[](https://dualhook.com/docs/contact-support#where-to-contact-dualhook) ------------------------------------------------------------------------------------------------- If you are already signed into Dualhook, the default support channel is the in-app support chat in the dashboard. Open the **Support** bubble in the bottom-right corner to start a new conversation or continue an existing one. This is the fastest way to contact Dualhook about: * setup questions * webhook and coexistence issues * template sync problems * billing questions tied to a workspace * bug reports and unexpected dashboard behavior Support conversations are organization-scoped, so the discussion stays attached to the right workspace and connection context. What the Dashboard Support Chat Is For[](https://dualhook.com/docs/contact-support#what-the-dashboard-support-chat-is-for) --------------------------------------------------------------------------------------------------------------------------- Use the dashboard support chat when you want help from the Dualhook team without leaving the product. Typical examples: * "Why is my webhook verified but message events are not arriving?" * "Can you check why this connection is showing `LIMITED`?" * "The template editor is failing when I upload media." Each organization can keep multiple support conversations. That makes it easier to separate onboarding questions, billing follow-up, and bug reports into distinct threads. If you are troubleshooting a live issue, first review [Troubleshooting](https://dualhook.com/docs/troubleshooting) , then run the connection **Debug** tab diagnostics and open a support chat with the exact failing step. What to Include in a Bug Report[](https://dualhook.com/docs/contact-support#what-to-include-in-a-bug-report) ------------------------------------------------------------------------------------------------------------- For the fastest resolution, include: 1. The affected organization and connection name. 2. What you expected to happen. 3. What actually happened. 4. Exact steps to reproduce the problem. 5. Approximate time of the issue and your timezone. 6. Relevant identifiers such as `waba_id`, `phone_number_id`, connection ID, or trace ID if you have them. If the issue involves webhook delivery, template sync, or health monitoring, linking the report to the relevant docs page can reduce back-and-forth: * [Webhook Override](https://dualhook.com/docs/webhook-override) * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) * [Maintain Account Health](https://dualhook.com/docs/account-health) Attachments and Response Flow[](https://dualhook.com/docs/contact-support#attachments-and-response-flow) --------------------------------------------------------------------------------------------------------- The support chat supports file attachments for screenshots, exported PDFs, and other supporting material. Current attachment rules: * images: JPG, PNG, GIF, WebP * documents: PDF * maximum file size: 4.5 MB per file When you send a new message in support chat, the Dualhook admin side receives an email notification so we can pick it up quickly. Replies appear in the same conversation thread inside your dashboard, and the support bubble shows an unread badge when there is a new response. For best results, attach: * screenshots of the failing UI state * PDF exports or incident notes * exact error text copied from the dashboard When to Use Email Instead[](https://dualhook.com/docs/contact-support#when-to-use-email-instead) ------------------------------------------------------------------------------------------------- Use `contact@dualhook.com` instead of the dashboard support chat when: * you cannot access the dashboard at all * the request is about privacy, GDPR, or a legal matter * you need a data deletion request handled * you are discussing something that is not tied to a specific Dualhook organization For privacy and deletion workflows, see [Compliance](https://dualhook.com/docs/compliance) and the [Privacy Policy](https://dualhook.com/privacy) . Related ------- * [Troubleshooting](https://dualhook.com/docs/troubleshooting) Common issues and solutions for Dualhook connections, webhooks, and health monitoring. * [Support FAQ](https://dualhook.com/docs/support-faq) Frequently asked questions about WhatsApp Business Platform and Dualhook. * [Quick Start](https://dualhook.com/docs/getting-started) Set up Dualhook and connect your first WhatsApp Business Account in minutes. * [About Dualhook](https://dualhook.com/docs/about) What Dualhook is, where it fits in the WhatsApp ecosystem, and how it works. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Troubleshooting | Dualhook Browse Docs Troubleshooting =============== Common issues and solutions for Dualhook connections, webhooks, and health monitoring. Before deeper WhatsApp Cloud API troubleshooting, open the connection's **Health** tab in Dualhook and run diagnostics. It pulls the latest Meta-side health, webhook routing, and account signals for that connection. Embedded Signup Completes But IDs Are Missing[](https://dualhook.com/docs/troubleshooting#embedded-signup-completes-but-ids-are-missing) ----------------------------------------------------------------------------------------------------------------------------------------- If Meta or the WhatsApp Business app shows Dualhook connected but the Connections page is empty, use the dedicated [Connection Not Appearing After Embedded Signup](https://dualhook.com/docs/connection-not-appearing) guide. If Meta shows an error code, use the [Embedded Signup Errors](https://dualhook.com/docs/embedded-signup-errors) index. **Likely causes:** * Popup `postMessage` did not arrive * Access did not include expected business assets **What to do:** 1. Retry in a browser profile with popups allowed. 2. Complete the flow fully without closing early. 3. Use Dualhook's [fallback discovery](https://dualhook.com/docs/embedded-signup) and finalize the selected WABA/number. Webhook Verified But No Message Events Arrive[](https://dualhook.com/docs/troubleshooting#webhook-verified-but-no-message-events-arrive) ----------------------------------------------------------------------------------------------------------------------------------------- **Likely causes:** * [Override](https://dualhook.com/docs/webhook-override) not active for this connection * Wrong endpoint URL or environment * [Heartbeat](https://dualhook.com/docs/heartbeat) overdue * Endpoint returns non-`200` or times out **What to do:** 1. Re-check the connection webhook URL and override status in the dashboard. 2. Send real inbound and outbound tests (not only the synthetic ping). 3. Check heartbeat status and acknowledge if overdue. 4. Check server logs for signature, parsing, or timeout errors. Messages Arrive at Wrong Endpoint[](https://dualhook.com/docs/troubleshooting#messages-arrive-at-wrong-endpoint) ----------------------------------------------------------------------------------------------------------------- **Likely causes:** * A manually configured phone-level callback taking precedence over Dualhook's WABA callback * Legacy configuration still active **What to do:** 1. Check whether somebody configured a phone-level callback directly in Meta. 2. Remove that manual override if Dualhook's WABA callback should be authoritative. 3. Re-subscribe the WABA intentionally. See [Webhook Override routing priority](https://dualhook.com/docs/webhook-override) for the precedence rules. Media Webhooks Arrive But No File Is Available[](https://dualhook.com/docs/troubleshooting#media-webhooks-arrive-but-no-file-is-available) ------------------------------------------------------------------------------------------------------------------------------------------- **Likely cause:** * The webhook contains media metadata and an ID, not the binary file itself. **What to do:** 1. Retrieve media using `GET /` — see [Upload, Retrieve & Delete Media](https://dualhook.com/docs/media-upload) . 2. Download using the returned temporary URL with bearer auth. Phone Number Stays `PENDING` or Says "Not Linked to Your WhatsApp Account"[](https://dualhook.com/docs/troubleshooting#phone-number-stays-pending-or-says-not-linked-to-your-whatsapp-account) ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- If Meta shows phone status `PENDING`, or the health output includes error `141000` saying the phone number is not linked to your WhatsApp account, the number is usually **ownership-verified but not fully registered for Cloud API sending yet**. **Likely causes:** * SMS or voice OTP verification completed, but Cloud API registration did not. * The number was added through an add-new-number flow, which does **not** use the coexistence QR/app-confirmation path. * The number was deleted and re-added, and a previous [two-step verification](https://dualhook.com/docs/two-step-verification) PIN is still in effect. **What to do:** 1. Confirm whether this is [coexistence](https://dualhook.com/docs/coexistence) or an add-new-number Cloud API setup. Existing WhatsApp Business App numbers in coexistence use the QR/app-confirmation flow; add-new-number flows do not. 2. Check the phone status in WhatsApp Manager or via Graph API. `PENDING` means not fully registered; `CONNECTED` means registration completed. 3. If phone ownership is not verified yet, request and verify the SMS or voice code first. 4. Register the phone on Cloud API with `POST //register` and a 6-digit PIN. Meta uses this call to complete registration and enable or confirm two-step verification. 5. If registration fails because a PIN already exists, use the previous PIN or disable/reset two-step verification in WhatsApp Manager, then retry registration. 6. If the number came from [Embedded Signup](https://dualhook.com/docs/embedded-signup) , complete registration within 14 days. After that window, you may need to rerun Embedded Signup. **What does not usually block registration by itself:** * Display name review still pending * Business verification still pending Those states can limit messaging capacity, but they are separate from the `PENDING` or `141000` registration problem. Quality Drops to LIMITED or BLOCKED[](https://dualhook.com/docs/troubleshooting#quality-drops-to-limited-or-blocked) --------------------------------------------------------------------------------------------------------------------- **What to do:** 1. Pause or throttle risky campaigns. 2. Prioritize utility and authentication traffic. 3. Review template quality, audience targeting, and opt-out handling. 4. Resume scale only after sustained quality recovery. See [Maintain Account Health](https://dualhook.com/docs/account-health) for a full incident playbook. Still Stuck? Contact Dualhook Support[](https://dualhook.com/docs/troubleshooting#still-stuck-contact-dualhook-support) ------------------------------------------------------------------------------------------------------------------------ If the steps above do not resolve the issue, open the in-app support chat from the Dualhook dashboard. For the fastest triage, include: * organization and connection name * the exact failing step * expected vs actual behavior * timestamp and timezone * screenshots, exported PDFs, or copied error text That support path is documented in [Support, Contact, and Bug Reports](https://dualhook.com/docs/contact-support) . Related ------- * [Quick Start](https://dualhook.com/docs/getting-started) Set up Dualhook and connect your first WhatsApp Business Account in minutes. * [Connection Not Appearing After Embedded Signup](https://dualhook.com/docs/connection-not-appearing) Why Meta can finish linking while no Dualhook connection appears, and how to retry the browser handoff safely. * [Embedded Signup Errors](https://dualhook.com/docs/embedded-signup-errors) Searchable Meta Embedded Signup error index with safe first actions for business, WABA, phone, OTP, sharing, and account restrictions. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Messaging Health Status](https://dualhook.com/docs/health-monitoring) Monitor phone number health: AVAILABLE, LIMITED, BLOCKED states and quality rating. * [Support, Contact, and Bug Reports](https://dualhook.com/docs/contact-support) How to contact Dualhook support, use the in-app support chat, report bugs, attach screenshots or PDFs, and know when email is the right path. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Dualhook Support FAQ: Setup, Webhook Routing, Billing and Bug Reports | Dualhook Browse Docs Support FAQ =========== Frequently asked questions about WhatsApp Business Platform and Dualhook. Answers to the questions Dualhook support gets most often: how to reach us and what to put in a bug report, Coexistence and webhook-routing questions, switching phone numbers, and WhatsApp Business Platform basics. If you are chasing a Meta error code instead, start at the [error reference](https://dualhook.com/docs/api-errors) . How Do I Contact Dualhook Support?[](https://dualhook.com/docs/support-faq#how-do-i-contact-dualhook-support) -------------------------------------------------------------------------------------------------------------- If you are signed into Dualhook, use the in-app support chat in the dashboard. Open the support bubble in the bottom-right corner to ask a question, continue an existing thread, or start a new conversation for a specific issue. This is the preferred support path for: * onboarding help * coexistence and webhook issues * dashboard questions * billing questions tied to your workspace * bug reports For the full support workflow, see [Support, Contact, and Bug Reports](https://dualhook.com/docs/contact-support) . How Do I Report a Bug or Unexpected Behavior?[](https://dualhook.com/docs/support-faq#how-do-i-report-a-bug-or-unexpected-behavior) ------------------------------------------------------------------------------------------------------------------------------------ Use the dashboard support chat and include: 1. what you expected 2. what happened instead 3. exact steps to reproduce 4. the affected connection or organization 5. screenshots, PDFs, or copied error text when available That gives Dualhook enough context to reproduce and triage the problem quickly. When Should I Use Email Instead of the Dashboard Chat?[](https://dualhook.com/docs/support-faq#when-should-i-use-email-instead-of-the-dashboard-chat) ------------------------------------------------------------------------------------------------------------------------------------------------------ Use `contact@dualhook.com` if you cannot access the dashboard, or if the request is about privacy, GDPR, data deletion, or another legal/compliance topic. What Is the WhatsApp Business Platform?[](https://dualhook.com/docs/support-faq#what-is-the-whatsapp-business-platform) ------------------------------------------------------------------------------------------------------------------------ It is Meta's API platform for programmatic WhatsApp messaging. In Dualhook setups, your backend sends through the connection-scoped Dualhook runtime while inbound message webhooks arrive directly from Meta via [Webhook Override](https://dualhook.com/docs/webhook-override) . For architecture details, see [About Dualhook](https://dualhook.com/docs/about) , [Messaging Overview](https://dualhook.com/docs/messaging-overview) , and [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) . How Do I Switch to a Different Phone Number?[](https://dualhook.com/docs/support-faq#how-do-i-switch-to-a-different-phone-number) ---------------------------------------------------------------------------------------------------------------------------------- Use a controlled cutover: 1. Add and verify the new number in the same [WABA](https://dualhook.com/docs/whatsapp-business-accounts) (or the target WABA). 2. Register and configure the new sender in your backend. 3. Subscribe and set [webhook override](https://dualhook.com/docs/webhook-override) for the new number/WABA path. 4. Run test traffic, then move production traffic. 5. Remove or disable the old number only after the cutover is stable. What Does My Business Need to Be Approved?[](https://dualhook.com/docs/support-faq#what-does-my-business-need-to-be-approved) ------------------------------------------------------------------------------------------------------------------------------ Baseline requirements usually include: * A valid [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) and [WABA](https://dualhook.com/docs/whatsapp-business-accounts) setup * [Display name](https://dualhook.com/docs/display-names) review and approval * Policy-compliant messaging behavior * Correct permissions and task scopes for the app or system user [Business verification](https://dualhook.com/docs/meta-business-verification) is often needed for higher trust, scale, and advanced capabilities. Which Countries Are Blocked?[](https://dualhook.com/docs/support-faq#which-countries-are-blocked) -------------------------------------------------------------------------------------------------- Availability can be restricted by Meta due to legal, sanctions, and policy constraints. Because this list can change, do not hardcode a static country list in operations. Recommended approach: * Validate supportability during onboarding per destination market. * Re-check Meta policy and availability guidance before launching in new geographies. Can I Access User Profile Pictures via API?[](https://dualhook.com/docs/support-faq#can-i-access-user-profile-pictures-via-api) -------------------------------------------------------------------------------------------------------------------------------- No reliable customer profile-picture API should be assumed for production workflows. Webhook payloads provide identity fields such as `wa_id` and profile name. With Meta's early April 2026 BSUID rollout and later username launch, `wa_id` may be omitted for users who adopt WhatsApp usernames. The new `user_id` (BSUID) field becomes the stable identifier. See the [BSUID transition guide](https://dualhook.com/docs/contacts-bsuid) for details. Can One API Number Message Another API Number?[](https://dualhook.com/docs/support-faq#can-one-api-number-message-another-api-number) -------------------------------------------------------------------------------------------------------------------------------------- Do not rely on API-number-to-API-number messaging as a core production pattern. For testing and QA, use standard consumer WhatsApp recipients whenever possible. Related ------- * [Quick Start](https://dualhook.com/docs/getting-started) Set up Dualhook and connect your first WhatsApp Business Account in minutes. * [About Dualhook](https://dualhook.com/docs/about) What Dualhook is, where it fits in the WhatsApp ecosystem, and how it works. * [Pricing](https://dualhook.com/docs/pricing) Dualhook subscription plans, what is included, and how pricing relates to Meta messaging fees. * [Support, Contact, and Bug Reports](https://dualhook.com/docs/contact-support) How to contact Dualhook support, use the in-app support chat, report bugs, attach screenshots or PDFs, and know when email is the right path. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Meta Business Portfolio | Dualhook Browse Docs Meta Business Portfolio ======================= The top-level container for business assets in Meta and why it matters for WhatsApp. What It Is[](https://dualhook.com/docs/meta-business-portfolio#what-it-is) --------------------------------------------------------------------------- A Meta Business Portfolio (formerly called Meta Business Manager) is the top-level container for business assets in Meta. For WhatsApp Cloud API, it is the container that owns one or more [WhatsApp Business Accounts (WABAs)](https://dualhook.com/docs/whatsapp-business-accounts) . Why It Matters for Dualhook[](https://dualhook.com/docs/meta-business-portfolio#why-it-matters-for-dualhook) ------------------------------------------------------------------------------------------------------------- To connect WhatsApp assets in Dualhook, your business must have a Meta Business Portfolio. Without it, you cannot complete standard WhatsApp platform onboarding. Key Concepts[](https://dualhook.com/docs/meta-business-portfolio#key-concepts) ------------------------------------------------------------------------------- * A company can have multiple portfolios, but one primary portfolio is usually the cleanest setup. * One portfolio can contain multiple WABAs. * A portfolio can register up to 20 [phone numbers](https://dualhook.com/docs/phone-numbers) (subject to Meta policy and account state). * Each portfolio has a unique [Business ID](https://dualhook.com/docs/meta-business-id) . Before Creating a New Portfolio[](https://dualhook.com/docs/meta-business-portfolio#before-creating-a-new-portfolio) --------------------------------------------------------------------------------------------------------------------- * Confirm one does not already exist for your organization. * Ensure the user creating it has the correct Meta Business permissions. * Use a real business name and business email that match your legal entity details. Creating duplicate portfolios without a clear reason often causes avoidable ownership and permission problems later. How to Create a Portfolio[](https://dualhook.com/docs/meta-business-portfolio#how-to-create-a-portfolio) --------------------------------------------------------------------------------------------------------- 1. Go to `business.facebook.com` and sign in. 2. Open the business selector (top-left menu). 3. Click **Create a business portfolio**. 4. Enter your business name and required contact details. 5. Complete the email verification flow if prompted. Creation via Embedded Signup[](https://dualhook.com/docs/meta-business-portfolio#creation-via-embedded-signup) --------------------------------------------------------------------------------------------------------------- A portfolio can also be created during [Embedded Signup](https://dualhook.com/docs/embedded-signup) , depending on the onboarding path and account state. This is useful when setting up WhatsApp assets directly during integration. Business Verification[](https://dualhook.com/docs/meta-business-portfolio#business-verification) ------------------------------------------------------------------------------------------------- A Business Portfolio can be verified by Meta. Verification can unlock additional capabilities, such as higher messaging capacity and eligibility for [Official Business Account](https://dualhook.com/docs/official-business-account) features. See [Meta Business Verification](https://dualhook.com/docs/meta-business-verification) for details on verification paths. Related ------- * [Meta Business ID](https://dualhook.com/docs/meta-business-id) How to find your Meta Business ID and the difference between Business ID and WABA ID. * [WhatsApp Business Accounts](https://dualhook.com/docs/whatsapp-business-accounts) What a WABA is, how it relates to phone numbers, and how ownership works. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Meta Business ID | Dualhook Browse Docs Meta Business ID ================ How to find your Meta Business ID and the difference between Business ID and WABA ID. What It Is[](https://dualhook.com/docs/meta-business-id#what-it-is) -------------------------------------------------------------------- The Meta Business ID is the unique ID of your [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) . It may also be called: * Business Portfolio ID * Meta Business Manager ID * Business Manager ID * FBM ID Dualhook support may request this ID when troubleshooting account ownership, permissions, or verification issues. How to Find Your Meta Business ID[](https://dualhook.com/docs/meta-business-id#how-to-find-your-meta-business-id) ------------------------------------------------------------------------------------------------------------------ 1. Sign in to Meta Business Suite. 2. Open **Settings**. 3. Go to **Business Info**. 4. Copy the **Business Portfolio ID** shown near the top of the page. The Business Portfolio ID is your Meta Business ID. How to Confirm Which Portfolio Owns a WABA[](https://dualhook.com/docs/meta-business-id#how-to-confirm-which-portfolio-owns-a-waba) ------------------------------------------------------------------------------------------------------------------------------------ In WhatsApp Manager, check the [WABA](https://dualhook.com/docs/whatsapp-business-accounts) details page and verify the owning Business Portfolio name. **Important:** The ID displayed under a WABA name is typically the `WABA ID`. The `WABA ID` and `Meta Business ID` are different identifiers. Common Issues[](https://dualhook.com/docs/meta-business-id#common-issues) -------------------------------------------------------------------------- ### I cannot see any WABAs in this Business Portfolio[](https://dualhook.com/docs/meta-business-id#i-cannot-see-any-wabas-in-this-business-portfolio) Possible reasons: * The portfolio has no WABAs yet. * You are viewing the wrong Business Portfolio. * Your user role does not have admin or full asset access. * WABA assets exist but are not shared with your user. ### Can I change my Meta Business ID?[](https://dualhook.com/docs/meta-business-id#can-i-change-my-meta-business-id) No. Meta Business IDs are permanent and cannot be edited. Related ------- * [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) The top-level container for business assets in Meta and why it matters for WhatsApp. * [WhatsApp Business Accounts](https://dualhook.com/docs/whatsapp-business-accounts) What a WABA is, how it relates to phone numbers, and how ownership works. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Business Profiles | Dualhook Browse Docs Business Profiles ================= Managing the public-facing WhatsApp business profile for your phone number. Overview[](https://dualhook.com/docs/business-profiles#overview) ----------------------------------------------------------------- The WhatsApp business profile is the public profile data attached to a [phone number](https://dualhook.com/docs/phone-numbers) . This is separate from the [display-name](https://dualhook.com/docs/display-names) review process, but both are user-facing. Typical profile fields: `about`, `address`, `description`, `email`, `websites`, `vertical`, and `profile_picture_url` (read/view output). Dualhook does not currently provide a dedicated business-profile editor UI. Manage fields in WhatsApp Manager or through the connection-scoped [`dh_live_` Runtime API](https://dualhook.com/docs/runtime-api-reference#business-profile) , which preserves the Meta phone-edge path while keeping the Meta token private. Prerequisite[](https://dualhook.com/docs/business-profiles#prerequisite) ------------------------------------------------------------------------- Before updating the profile, make sure the number's [display name](https://dualhook.com/docs/display-names) is approved. If display-name approval is pending or rejected, profile changes can be blocked or delayed. Field Rules and Limits[](https://dualhook.com/docs/business-profiles#field-rules-and-limits) --------------------------------------------------------------------------------------------- * `about`: maximum 139 characters. * `description`: maximum 256 characters. * `websites`: up to 2 URLs. * `vertical`: use a supported Meta category value. Common `vertical` values include: `AUTO`, `BEAUTY`, `APPAREL`, `EDU`, `ENTERTAIN`, `EVENT_PLAN`, `FINANCE`, `GROCERY`, `GOVT`, `HOTEL`, `HEALTH`, `NONPROFIT`, `PROF_SERVICES`, `RETAIL`, `TRAVEL`, `RESTAURANT`, `NOT_A_BIZ`, `OTHER`. Reading the Business Profile[](https://dualhook.com/docs/business-profiles#reading-the-business-profile) --------------------------------------------------------------------------------------------------------- You can fetch current profile values through the Cloud API for the phone number. Use the profile endpoint for your `phone_number_id` and request the fields you need: `about,address,description,email,profile_picture_url,websites,vertical` Updating the Business Profile[](https://dualhook.com/docs/business-profiles#updating-the-business-profile) ----------------------------------------------------------------------------------------------------------- Update requests are partial — send only the fields you want to change. For a Coexistence connection, update the profile in the WhatsApp Business app. Meta does not support profile updates through the API for that connection mode; Dualhook returns `409` instead of sending an unsupported write. * Clear a text field by sending an empty string. * Clear websites by sending an empty array (`[]`). * Keep payloads minimal to avoid accidental overwrites. For profile photos, upload media first and obtain a valid `profile_picture_handle`. Use that handle in the business-profile update request. The `profile_picture_url` field is generally a response/read field, not the write field for updates. Best Practices[](https://dualhook.com/docs/business-profiles#best-practices) ----------------------------------------------------------------------------- * Keep profile data consistent with your verified business identity. * Use a supportable contact email and a stable business website. * Avoid frequent unnecessary edits during active review periods. * Ensure the profile wording matches your [display name](https://dualhook.com/docs/display-names) and public brand pages. Troubleshooting[](https://dualhook.com/docs/business-profiles#troubleshooting) ------------------------------------------------------------------------------- ### Update request succeeds but UI does not change immediately[](https://dualhook.com/docs/business-profiles#update-request-succeeds-but-ui-does-not-change-immediately) Profile updates can have propagation delay across clients and regions. Recheck in WhatsApp Manager after a short delay. ### Website or email rejected/ignored[](https://dualhook.com/docs/business-profiles#website-or-email-rejectedignored) Validate formatting and ensure the value is business-relevant. Remove unsupported characters or malformed URLs. ### Profile picture does not update[](https://dualhook.com/docs/business-profiles#profile-picture-does-not-update) Confirm you used a valid `profile_picture_handle`. Re-upload media if the handle expired or is invalid. Related ------- * [Display Names](https://dualhook.com/docs/display-names) How display names work, naming rules, review statuses, and common rejection patterns. * [Phone Numbers](https://dualhook.com/docs/phone-numbers) Managing phone numbers in WhatsApp Business Platform: adding, removing, and limits. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Two-Step Verification (2FA) | Dualhook Browse Docs Two-Step Verification (2FA) =========================== Phone-number-level security using a 6-digit PIN for WhatsApp Business Platform. What It Is[](https://dualhook.com/docs/two-step-verification#what-it-is) ------------------------------------------------------------------------- Two-step verification (2FA) is a [phone-number](https://dualhook.com/docs/phone-numbers) \-level security control for WhatsApp Business Platform. It uses a 6-digit PIN that is required when registering or re-registering a number. Why It Matters[](https://dualhook.com/docs/two-step-verification#why-it-matters) --------------------------------------------------------------------------------- If 2FA is enabled, registration-related actions for that number require the PIN. This is especially relevant during migrations between setups, [WABAs](https://dualhook.com/docs/whatsapp-business-accounts) , or [Meta Business Portfolios](https://dualhook.com/docs/meta-business-portfolio) . Who Can Manage 2FA[](https://dualhook.com/docs/two-step-verification#who-can-manage-2fa) ----------------------------------------------------------------------------------------- Only users with sufficient access in the owning Meta Business Portfolio can disable or reconfigure 2FA. When a phone number is registered through Dualhook (via the Register Phone Number action), Dualhook sends the PIN to Meta for that registration request but does not store it. Store the PIN in your own credential manager. How to Disable 2FA in Meta[](https://dualhook.com/docs/two-step-verification#how-to-disable-2fa-in-meta) --------------------------------------------------------------------------------------------------------- 1. Open **Meta Business Manager** → **WhatsApp Manager**. 2. Go to **Phone numbers** and select the target number. 3. Open **Settings** → **Two-step verification**. 4. Click **Turn off two-step verification**. 5. Confirm via the email link sent to the business portfolio email address. After Disabling[](https://dualhook.com/docs/two-step-verification#after-disabling) ----------------------------------------------------------------------------------- * Complete the migration or registration task that required 2FA to be disabled. * Re-enable 2FA afterward to restore stronger protection on the number. * If registering through Dualhook, store the PIN in a secure credential manager with controlled access. Dualhook does not save it for later recovery. Troubleshooting[](https://dualhook.com/docs/two-step-verification#troubleshooting) ----------------------------------------------------------------------------------- ### Turn off button is missing[](https://dualhook.com/docs/two-step-verification#turn-off-button-is-missing) * Insufficient permissions for the current user. * Wrong Business Portfolio or wrong WABA/number selected. ### Confirmation email does not arrive[](https://dualhook.com/docs/two-step-verification#confirmation-email-does-not-arrive) * Wrong or outdated portfolio email settings. * Email filtering or spam quarantine delays. ### Registration still asks for PIN[](https://dualhook.com/docs/two-step-verification#registration-still-asks-for-pin) * Disable request not fully completed through email confirmation. * Propagation delay after settings change. Related ------- * [Phone Numbers](https://dualhook.com/docs/phone-numbers) Managing phone numbers in WhatsApp Business Platform: adding, removing, and limits. * [WhatsApp Business Accounts](https://dualhook.com/docs/whatsapp-business-accounts) What a WABA is, how it relates to phone numbers, and how ownership works. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Meta Business Verification for WhatsApp: PLBV vs Standard vs Meta Verified | Dualhook Browse Docs Meta Business Verification ========================== Overview of verification paths: PLBV, Standard, and Meta Verified for Business. What It Is[](https://dualhook.com/docs/meta-business-verification#what-it-is) ------------------------------------------------------------------------------ Meta Business Verification confirms that a business is legitimate inside Meta's ecosystem. Verified status can improve trust, unlock additional platform capabilities, and support growth across Facebook, Instagram, and WhatsApp assets. Why It Matters[](https://dualhook.com/docs/meta-business-verification#why-it-matters) -------------------------------------------------------------------------------------- * Higher trust signal for customers * Access to advanced WhatsApp capabilities and scale paths * Better eligibility foundation for identity features such as [OBA](https://dualhook.com/docs/official-business-account) requests Verification Paths[](https://dualhook.com/docs/meta-business-verification#verification-paths) ---------------------------------------------------------------------------------------------- Businesses usually choose one of these paths depending on account state: * **Partner-led Business Verification (PLBV)** — WhatsApp-focused, started by a Solution Partner (BSP) during its onboarding. Not offered by Dualhook. * **[Standard Business Verification](https://dualhook.com/docs/standard-business-verification) ** — all Meta assets, requires website and domain-based email * **[Meta Verified for Business](https://dualhook.com/docs/meta-verified-for-business) ** — paid subscription across eligible assets [Coexistence](https://dualhook.com/docs/coexistence) caveat: Standard Business Verification is not available for coexistence accounts. Coexistence accounts rely on PLBV through a Solution Partner or on Meta Verified for Business where Meta offers it for the business's country and account; with Dualhook that means Meta Verified for Business, and Dualhook cannot influence eligibility. Verified Partner vs Verified Business[](https://dualhook.com/docs/meta-business-verification#verified-partner-vs-verified-business) ------------------------------------------------------------------------------------------------------------------------------------ Searches for a "Facebook verified partner" or "Meta verified partner" usually mix up two unrelated things: * **Business verification** is about _your_ business. It confirms your legal entity to Meta and unlocks WhatsApp capabilities on your WABA. That is what the rest of this page covers. * **Meta Business Partner** status is about a _vendor_. Companies that pass Meta's partner program review, such as Dualhook as a [Meta Tech Partner](https://dualhook.com/tech-partner) , carry a partner badge in Meta's directory. It says the vendor was reviewed, not that any of its customers are verified. Connecting through a badged partner does not verify your business, and it is not a shortcut around PLBV, Standard Business Verification, or Meta Verified. Partner-led Business Verification is a service of **Solution Partners (BSPs)**, who start it inside their own onboarding. **Dualhook is a Tech Partner, not a Solution Partner, and does not offer PLBV.** Dualhook customers verify through Standard Business Verification or Meta Verified for Business. Coexistence accounts cannot use the standard path, so Meta Verified for Business is the remaining option where Meta offers it for the business's country and account; eligibility is decided by Meta, not by Dualhook. The evidence Meta asks for is the same on every path. Comparison[](https://dualhook.com/docs/meta-business-verification#comparison) ------------------------------------------------------------------------------ | Method | Scope | Typical timing | Requirements | Review time | | --- | --- | --- | --- | --- | | PLBV (Solution Partners only, not Dualhook) | WhatsApp-focused | During BSP onboarding | Legal business docs | Minutes to ~48h | | Standard | All Meta assets | After portfolio setup | Website + domain email + official docs | Up to 14 business days | | Meta Verified | Subscription | When eligible | Paid plan + eligible assets + policy checks | ~1–3 business days | Document Readiness[](https://dualhook.com/docs/meta-business-verification#document-readiness) ---------------------------------------------------------------------------------------------- Accepted documents vary by country and region. Before submitting, prepare: * Legal business registration proof * Address or phone proof matching legal name * Consistent business identity across website, email domain, and [Meta portfolio](https://dualhook.com/docs/meta-business-portfolio) details Related ------- * [Standard Business Verification](https://dualhook.com/docs/standard-business-verification) The standard verification flow through Meta Security Center for non-coexistence accounts. * [Meta Verified for Business](https://dualhook.com/docs/meta-verified-for-business) Paid subscription for trust, badge, and brand-protection features across Meta assets. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Standard Business Verification | Dualhook Browse Docs Standard Business Verification ============================== The standard verification flow through Meta Security Center for non-coexistence accounts. Important Limitation[](https://dualhook.com/docs/standard-business-verification#important-limitation) ------------------------------------------------------------------------------------------------------ Standard Business Verification is **not** supported for [coexistence](https://dualhook.com/docs/coexistence) accounts. For coexistence setups, see [Meta Verified for Business](https://dualhook.com/docs/meta-verified-for-business) . Before You Start[](https://dualhook.com/docs/standard-business-verification#before-you-start) ---------------------------------------------------------------------------------------------- * Active HTTPS website with business details matching [portfolio](https://dualhook.com/docs/meta-business-portfolio) records * Business email on the same website domain * Accurate legal or trade name consistency across documents and online presence * Verification phone (if used) able to receive SMS or calls Only an authorized representative of the business should upload verification documents. Standard Flow[](https://dualhook.com/docs/standard-business-verification#standard-flow) ---------------------------------------------------------------------------------------- 1. Open **Meta Business Suite** → **Security Center** → **Start Verification**. 2. Select your business (or continue with manual document flow if not listed). 3. Verify via code (SMS, call, or email) or use the verified domain route. 4. Upload legal-name proof and address/phone proof when requested. 5. Submit and wait for review. 6. Track result in Security Center. Troubleshooting[](https://dualhook.com/docs/standard-business-verification#troubleshooting) -------------------------------------------------------------------------------------------- ### Start Verification button is missing[](https://dualhook.com/docs/standard-business-verification#start-verification-button-is-missing) * Portfolio is not yet eligible. * Required Business Info fields are incomplete. * WhatsApp setup is still in a limited-access stage. ### Verification rejected[](https://dualhook.com/docs/standard-business-verification#verification-rejected) * Review the rejection reason in Security Center. * Open a Meta support case from the rejection card. * Resubmit with corrected documents that exactly match legal and portfolio details. Ownership Warning[](https://dualhook.com/docs/standard-business-verification#ownership-warning) ------------------------------------------------------------------------------------------------ The [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) used for verification should be owned by the real business entity that sends messages. Poor ownership hygiene or unrelated user access can increase account risk. Related ------- * [Meta Business Verification](https://dualhook.com/docs/meta-business-verification) Overview of verification paths: PLBV, Standard, and Meta Verified for Business. * [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) The top-level container for business assets in Meta and why it matters for WhatsApp. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Meta Verified for Business | Dualhook Browse Docs Meta Verified for Business ========================== Paid subscription for trust, badge, and brand-protection features across Meta assets. What It Is[](https://dualhook.com/docs/meta-verified-for-business#what-it-is) ------------------------------------------------------------------------------ Meta Verified for Business is a paid subscription that adds trust and brand-protection features across eligible Meta assets. Typical Benefits[](https://dualhook.com/docs/meta-verified-for-business#typical-benefits) ------------------------------------------------------------------------------------------ Plan tiers can differ by market, but often include: * Verified badge on selected assets * Impersonation protection * Enhanced support access * Profile visibility enhancements Eligibility Checklist[](https://dualhook.com/docs/meta-verified-for-business#eligibility-checklist) ---------------------------------------------------------------------------------------------------- Common prerequisites include: * Applicant meets age-of-majority requirement. * Business is in a country where Meta Verified is available. * Full control of an eligible [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) . * Minimum activity requirements are met. * Recent account-profile changes stay within Meta thresholds. * Agreement with Meta terms and platform policies. Subscription Flow[](https://dualhook.com/docs/meta-verified-for-business#subscription-flow) -------------------------------------------------------------------------------------------- 1. Confirm eligibility in Meta Business Suite. 2. Review current Meta Verified requirements for your portfolio and assets. 3. Start subscription for eligible assets and complete payment/verification steps. After Activation[](https://dualhook.com/docs/meta-verified-for-business#after-activation) ------------------------------------------------------------------------------------------ * Eligible assets show verified status. * Additional support and protection features become available. * Subscription can typically be managed, upgraded, or canceled in Business Suite. Coexistence Context[](https://dualhook.com/docs/meta-verified-for-business#coexistence-context) ------------------------------------------------------------------------------------------------ For [coexistence](https://dualhook.com/docs/coexistence) accounts that cannot use [Standard Business Verification](https://dualhook.com/docs/standard-business-verification) , Meta Verified for Business can be a practical alternative path for business trust and protection features. Related ------- * [Meta Business Verification](https://dualhook.com/docs/meta-business-verification) Overview of verification paths: PLBV, Standard, and Meta Verified for Business. * [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) How Coexistence mode works: using WhatsApp Business App and Cloud API on the same number. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Official Business Account (OBA) | Dualhook Browse Docs Official Business Account (OBA) =============================== The WhatsApp blue checkmark: eligibility, notability requirements, and limitations. What OBA Is[](https://dualhook.com/docs/official-business-account#what-oba-is) ------------------------------------------------------------------------------- An Official Business Account (OBA) is the WhatsApp business account status that shows a blue checkmark next to the [display name](https://dualhook.com/docs/display-names) . OBA is granted by Meta for businesses that are authentic and notable. Coexistence Limitation[](https://dualhook.com/docs/official-business-account#coexistence-limitation) ----------------------------------------------------------------------------------------------------- OBA is **not** supported for [Coexistence](https://dualhook.com/docs/coexistence) accounts. If your number is running in Coexistence mode, you cannot use OBA for that number. Evaluate [Meta Verified for Business](https://dualhook.com/docs/meta-verified-for-business) options instead. Eligibility Criteria[](https://dualhook.com/docs/official-business-account#eligibility-criteria) ------------------------------------------------------------------------------------------------- Before requesting OBA, make sure all of the following are true: * Your business complies with WhatsApp Business Messaging Policy. * The business is active on WhatsApp Business Platform for at least 30 days. * The [business portfolio](https://dualhook.com/docs/meta-business-portfolio) owning the number is business-verified by Meta. * The phone number has [two-step verification](https://dualhook.com/docs/two-step-verification) enabled. * The [display name](https://dualhook.com/docs/display-names) is approved. * The brand is notable (well-known and frequently searched). Notability: What Meta Looks For[](https://dualhook.com/docs/official-business-account#notability-what-meta-looks-for) ---------------------------------------------------------------------------------------------------------------------- Notability is about public recognition, not basic account authenticity. Meta reviews external signals such as press coverage from credible publications. * Use recent third-party coverage (generally from the last 12 months). * Do not rely on your own site, social profiles, paid placements, or sponsored content as primary evidence. * Keep your website business details consistent with your display name and company identity. How to Request OBA[](https://dualhook.com/docs/official-business-account#how-to-request-oba) --------------------------------------------------------------------------------------------- 1. Open **WhatsApp Manager**. 2. Go to **Account Tools** → **Phone Numbers**. 3. Select the target number. 4. Open **Profile**. 5. Under Official business account, click **Submit request** if available. 6. Add supporting evidence links and complete the request form. If the submit button is unavailable or grayed out, the account is currently not eligible. Denials and Re-Apply Window[](https://dualhook.com/docs/official-business-account#denials-and-re-apply-window) --------------------------------------------------------------------------------------------------------------- If Meta rejects the OBA request, there is no direct appeal path. You can strengthen your public notability signals and submit again after 30 days. Display Name Changes After Approval[](https://dualhook.com/docs/official-business-account#display-name-changes-after-approval) ------------------------------------------------------------------------------------------------------------------------------- If you change the [display name](https://dualhook.com/docs/display-names) after OBA is granted, Meta may reassess the number for notability and compliance. OBA status is not guaranteed to remain after a display-name change. Cost and Ownership[](https://dualhook.com/docs/official-business-account#cost-and-ownership) --------------------------------------------------------------------------------------------- There is no separate fee to apply for OBA itself. Approval decisions are fully controlled by Meta. Neither Dualhook nor any BSP can guarantee approval. Related ------- * [Display Names](https://dualhook.com/docs/display-names) How display names work, naming rules, review statuses, and common rejection patterns. * [Meta Business Verification](https://dualhook.com/docs/meta-business-verification) Overview of verification paths: PLBV, Standard, and Meta Verified for Business. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Business Phone Numbers: Add, Register, Limits, Remove | Dualhook Browse Docs Phone Numbers ============= Managing phone numbers in WhatsApp Business Platform: adding, removing, and limits. How WhatsApp Business Platform phone numbers behave when you connect through Dualhook: where to find the `phone_number_id`, how OTP registration and the registered-number cap work, what changes for a [Coexistence](https://dualhook.com/whatsapp-coexistence) number, and why removing a number is harder than adding one. The numbers stay in your own Meta assets; Dualhook configures them but never owns them. Overview[](https://dualhook.com/docs/phone-numbers#overview) ------------------------------------------------------------- Phone numbers are the sending identity for WhatsApp Business Platform. Each number is linked to a [WABA](https://dualhook.com/docs/whatsapp-business-accounts) and can be used for app, [coexistence](https://dualhook.com/docs/coexistence) , and API workflows depending on setup. * A phone number can only be actively attached to one WhatsApp business setup at a time. * Number ownership, quality, and policy status affect messaging eligibility. * In Dualhook, numbers are managed in your own Meta assets — Dualhook does not own your numbers. * A connection-scoped `dh_live_...` key can read the selected number's allowlisted identity, quality, status, capability, name-status, messaging limit, and health fields through the [Runtime API](https://dualhook.com/docs/runtime-api-reference#phone-and-waba-health) . Where to Find Phone Numbers in Meta[](https://dualhook.com/docs/phone-numbers#where-to-find-phone-numbers-in-meta) ------------------------------------------------------------------------------------------------------------------- 1. Open Meta Business settings. 2. Go to **Accounts** → **WhatsApp Accounts**. 3. Select your WABA. 4. Open **WhatsApp Manager** and go to **Phone numbers**. From this view you can usually see the display phone number, verification and name status, and the `phone_number_id` used for API sending. Adding a Phone Number[](https://dualhook.com/docs/phone-numbers#adding-a-phone-number) --------------------------------------------------------------------------------------- Common path: 1. In WhatsApp Manager, open **Phone numbers**. 2. Click **Add phone number**. 3. Choose country code and enter the number. 4. Complete OTP verification (SMS or voice). 5. Register the phone for Cloud API sending if Meta still shows the number as `PENDING`. 6. Complete [display name](https://dualhook.com/docs/display-names) review steps if prompted. **Important:** * OTP verification proves you control the phone number, but it does **not** always mean the number is fully registered on WhatsApp Business Platform. * In add-new-number Cloud API flows, Meta can leave the number in `PENDING` until registration is completed with `POST //register`. * That registration call requires a 6-digit PIN and enables or confirms [two-step verification](https://dualhook.com/docs/two-step-verification) . * A fully registered Cloud API number should move to `CONNECTED`. * This is different from [coexistence](https://dualhook.com/docs/coexistence) , where an existing WhatsApp Business App number follows the QR/app-confirmation flow instead of the add-new-number path. You can onboard numbers through [Embedded Signup](https://dualhook.com/docs/embedded-signup) and then manage them in Dualhook, or add them in Meta first and then connect through Dualhook. Registered Number Cap[](https://dualhook.com/docs/phone-numbers#registered-number-cap) --------------------------------------------------------------------------------------- New business portfolios start capped at **2 registered business phone numbers**. The cap automatically increases to **20** when: * Your business becomes [verified](https://dualhook.com/docs/meta-business-verification) , **or** * You reach a messaging limit of 2,000. When this happens, Meta sends a Business Suite notification and a `business_capability_update` webhook with `max_phone_numbers_per_business` set to your new cap. If you're forwarding management webhooks via Dualhook, you'll see this event flow through. Toll-Free (1-800) and IVR Numbers[](https://dualhook.com/docs/phone-numbers#toll-free-1-800-and-ivr-numbers) ------------------------------------------------------------------------------------------------------------- You can register a 1-800 or other toll-free number, but the WhatsApp registration call **cannot navigate an IVR**. Numbers behind an IVR system can be registered if they accept calls from international numbers and can redirect Meta's SMS or voice call to a real person or mailbox. To register a number behind an IVR: 1. Meta shares 1 or 2 phone numbers that the registration call will originate from. 2. Allow-list those numbers in your IVR. If you can't allow-list, add the number to your WABA and open a Direct Support ticket asking for the registration-call source numbers. 3. Redirect the registration call to an employee or mailbox to capture the 6-digit code. 4. Submit the code via `POST //verify_code` with `code=`. Numbers behind an IVR that can't accept registration calls are not supported. Identity Change Check[](https://dualhook.com/docs/phone-numbers#identity-change-check) --------------------------------------------------------------------------------------- Optional security feature. When enabled, Meta tells you whether the WhatsApp account behind a customer's phone number has changed since you last messaged them — protecting against recycled-phone-number attacks. **How it works:** 1. Enable on the phone number's settings: POST //settings { "user_identity_change": { "enable_identity_key_check": true } } The same safe setting is available at `https://api.dualhook.com/v25.0//settings` with the connection's `dh_live_...` key. 2. Subsequent inbound message webhooks include the user's `identity_key_hash` in the `contacts` object. Outbound status webhooks include `recipient_identity_key_hash` in the `statuses` object. 3. Store the hash per customer. 4. On future sends, include `recipient_identity_key_hash` in the request body. Meta compares it to the customer's current hash: * **Match** → message delivered. * **Mismatch** → message blocked, status webhook with error code `137000` returned. When you receive a `137000` mismatch, treat the customer's phone number as no longer trusted. Re-verify identity through a non-WhatsApp channel before resending. Send the next message **without** a hash, then capture and store the new hash from the resulting status webhook. This is the same identity-binding pattern recommended for [authentication-message OTP delivery](https://dualhook.com/docs/authentication-best-practices#identity-hash-binding) . // Send with check { "messaging_product": "whatsapp", "to": "+16505551234", "recipient_identity_key_hash": "DF2lS5v2W6x=", "type": "text", "text": { "body": "Your latest statement is attached." } } Portfolio and Number Limits[](https://dualhook.com/docs/phone-numbers#portfolio-and-number-limits) --------------------------------------------------------------------------------------------------- Portfolio-level number capacity is controlled by Meta account state. In many setups, businesses start with a lower number limit and can increase over time as trust and [verification](https://dualhook.com/docs/meta-business-verification) improve. * Keep business verification and policy compliance in good standing. * Monitor [quality and messaging limits](https://dualhook.com/docs/capacity-quality-limits) per number. * Do not assume every portfolio has the same number capacity from day one. Removing a Phone Number[](https://dualhook.com/docs/phone-numbers#removing-a-phone-number) ------------------------------------------------------------------------------------------- Before deleting any number: * Stop active sends and campaigns that use the number. * Disconnect or update any webhook/send logic that points to that number. * In Dualhook, disconnect or update the related connection first. Typical Meta removal flow: 1. Open **WhatsApp Manager** → **Phone numbers**. 2. Select the target number. 3. Use the remove/delete action (trash/bin icon). 4. Confirm the removal prompt. **Important:** * Deletion can be restricted by permissions, billing/account state, or recent paid usage. * Meta may apply cooling periods before some numbers can be removed. * Only users with sufficient Business admin permissions can complete deletion. Common Issues[](https://dualhook.com/docs/phone-numbers#common-issues) ----------------------------------------------------------------------- ### Delete button is missing[](https://dualhook.com/docs/phone-numbers#delete-button-is-missing) * Your role lacks admin rights. * The number is controlled by another portfolio or partner setup. * The number is in a state that blocks removal. ### Number cannot be added[](https://dualhook.com/docs/phone-numbers#number-cannot-be-added) * Number is already registered elsewhere. * OTP verification failed or timed out. * Number format, provider restrictions, or unsupported type. ### Messages fail after number changes[](https://dualhook.com/docs/phone-numbers#messages-fail-after-number-changes) * Old `phone_number_id` is still used in sending code. * [Webhook routing](https://dualhook.com/docs/webhook-override) still points to the previous setup. ### Number stays `PENDING` after verification[](https://dualhook.com/docs/phone-numbers#number-stays-pending-after-verification) * OTP verification completed, but Cloud API registration did not. * The number still needs `POST //register` with a 6-digit PIN. * If the number was deleted and re-added, a previous [two-step verification](https://dualhook.com/docs/two-step-verification) PIN may still be required. * In [Embedded Signup](https://dualhook.com/docs/embedded-signup) , registration must be completed within 14 days or the signup flow may need to be run again. Related ------- * [WhatsApp Business Accounts](https://dualhook.com/docs/whatsapp-business-accounts) What a WABA is, how it relates to phone numbers, and how ownership works. * [Display Names](https://dualhook.com/docs/display-names) How display names work, naming rules, review statuses, and common rejection patterns. * [Messaging Health Status](https://dualhook.com/docs/health-monitoring) Monitor phone number health: AVAILABLE, LIMITED, BLOCKED states and quality rating. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Display Names | Dualhook Browse Docs Display Names ============= How display names work, naming rules, review statuses, and common rejection patterns. Overview[](https://dualhook.com/docs/display-names#overview) ------------------------------------------------------------- The display name is the business name shown to users in WhatsApp surfaces for your business number. It is reviewed by Meta and must follow naming policy before it is fully approved. * Display name and `phone_number_id` are different things. * A number can be technically connected while its display name is still under review. Where to Manage Display Names[](https://dualhook.com/docs/display-names#where-to-manage-display-names) ------------------------------------------------------------------------------------------------------- Use Meta WhatsApp Manager for display-name submissions and updates: 1. Open **WhatsApp Manager**. 2. Go to **Phone numbers**. 3. Select the number. 4. Open the profile/display-name settings. 5. Submit a new display name or edit an existing one. Display Name Statuses[](https://dualhook.com/docs/display-names#display-name-statuses) --------------------------------------------------------------------------------------- Typical lifecycle: * `PENDING_REVIEW` — submitted and waiting for Meta review. * `APPROVED` — accepted and active. * `AVAILABLE_WITHOUT_REVIEW` — ready to use without a separate review step. * `REJECTED` / `DECLINED` — not accepted; update and resubmit. * `NON_EXISTS` — no submitted display-name review record is available yet. You can monitor relevant management events and logs in Dualhook. Source-of-truth review decisions still come from Meta. Naming Rules[](https://dualhook.com/docs/display-names#naming-rules) --------------------------------------------------------------------- Use a name that clearly identifies your real business. Recommended checklist before submitting: * Match your legal or commonly used brand identity. * Keep formatting natural — no spammy capitalization, symbols, or decorative text. * Avoid generic names that do not identify a specific business. * Ensure your public web presence (domain, site, social pages) supports the same brand name. * If operating under a sub-brand, make the relationship to the parent business clear. How Often You Can Change[](https://dualhook.com/docs/display-names#how-often-you-can-change) --------------------------------------------------------------------------------------------- After a number is registered, display-name changes are limited to one change every 30 days per number. If Meta updates this policy for your account, follow the latest rule shown in WhatsApp Manager. Why Display Names Get Rejected[](https://dualhook.com/docs/display-names#why-display-names-get-rejected) --------------------------------------------------------------------------------------------------------- Common rejection patterns: * Name does not match business identity evidence. * Name is overly generic or misleading. * Name includes disallowed formatting or promotional styling. * Supporting business information is incomplete or inconsistent. Troubleshooting[](https://dualhook.com/docs/display-names#troubleshooting) --------------------------------------------------------------------------- ### Status stays pending for too long[](https://dualhook.com/docs/display-names#status-stays-pending-for-too-long) * For coexistence / Embedded Signup numbers, `name_status = NON_EXISTS` together with `new_name_status = PENDING_REVIEW` can be an expected holding state until paid Meta verification is completed. By itself, that pattern is not a blocker. * Verify business information is complete in your [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) . * Confirm the submitted name matches your public-facing brand assets. * Avoid repeated edits while a review is still in progress. ### Name approved but not visible everywhere[](https://dualhook.com/docs/display-names#name-approved-but-not-visible-everywhere) * Client-side propagation can take time across regions and devices. * Recheck after a propagation delay and verify you are viewing the correct number/account. ### `NON_EXISTS` appears[](https://dualhook.com/docs/display-names#non_exists-appears) * On non-coexistence setups, `NON_EXISTS` usually means no valid display-name submission is currently attached to that number. * On coexistence setups, `NON_EXISTS` can also appear together with `new_name_status = PENDING_REVIEW` as an expected holding state rather than a missing submission, especially before paid Meta verification is completed. * Reopen the number profile in WhatsApp Manager and resubmit only when the account context and current setup path indicate the submission is actually missing or invalid. Related ------- * [Phone Numbers](https://dualhook.com/docs/phone-numbers) Managing phone numbers in WhatsApp Business Platform: adding, removing, and limits. * [Business Profiles](https://dualhook.com/docs/business-profiles) Managing the public-facing WhatsApp business profile for your phone number. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Business Accounts | Dualhook Browse Docs WhatsApp Business Accounts ========================== What a WABA is, how it relates to phone numbers, and how ownership works. Looking for the high-level overview first? Start with [WhatsApp Business API](https://dualhook.com/whatsapp-business-api) and then return here for the implementation details. What a WABA Is[](https://dualhook.com/docs/whatsapp-business-accounts#what-a-waba-is) -------------------------------------------------------------------------------------- A WhatsApp Business Account (WABA) is the top-level WhatsApp container inside a [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) . It groups WhatsApp assets such as: * [Phone numbers](https://dualhook.com/docs/phone-numbers) * [Message templates](https://dualhook.com/docs/templates) * Webhook subscriptions and messaging-related configuration In practice, a WABA is the business object your integration targets for setup and management operations. WABA ID[](https://dualhook.com/docs/whatsapp-business-accounts#waba-id) ------------------------------------------------------------------------ Each WABA has a unique `waba_id`. You use this ID for account-level actions, for example: * App subscription setup (`/subscribed_apps`) * Account-level webhook and management operations WABA vs Phone Number ID[](https://dualhook.com/docs/whatsapp-business-accounts#waba-vs-phone-number-id) -------------------------------------------------------------------------------------------------------- * `waba_id` identifies the WhatsApp Business Account container. * `phone_number_id` identifies a specific sending number inside that WABA. Both IDs are required in production integrations, but for different API operations. Ownership Model[](https://dualhook.com/docs/whatsapp-business-accounts#ownership-model) ---------------------------------------------------------------------------------------- WABAs are owned by a Meta Business Portfolio. A business can have multiple WABAs, and each WABA can contain multiple phone numbers, subject to Meta limits and account state. In Dualhook: * You keep ownership of your WABAs and numbers in your own Meta assets. * Dualhook configures [coexistence](https://dualhook.com/docs/coexistence) and [webhook routing](https://dualhook.com/docs/webhook-override) against those assets. * Dualhook does not become legal owner of your WABA. * A connection-scoped `dh_live_...` key can read a safe allowlist of WABA identity, review, verification, health, messaging-limit, timezone, currency, and template-namespace fields. It cannot enumerate other WABAs or expose owner/funding data. See the [Runtime API reference](https://dualhook.com/docs/runtime-api-reference#phone-and-waba-health) . Creating a WABA[](https://dualhook.com/docs/whatsapp-business-accounts#creating-a-waba) ---------------------------------------------------------------------------------------- Common onboarding paths: 1. Create or use an existing [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) . 2. Complete required [business verification](https://dualhook.com/docs/meta-business-verification) steps when applicable. 3. Create the WABA during [Embedded Signup](https://dualhook.com/docs/embedded-signup) or via Meta business tooling. 4. Add and verify phone numbers under the WABA. For Dualhook customers, Embedded Signup is the standard path because it automates ID discovery and routing setup. Access and Permissions[](https://dualhook.com/docs/whatsapp-business-accounts#access-and-permissions) ------------------------------------------------------------------------------------------------------ WABA management depends on Meta roles and task permissions. Without sufficient permissions, users may be unable to: * View WABA details * Manage phone numbers * Update templates * Configure webhook subscriptions If something appears missing in UI or API, verify you are in the correct Business Portfolio and that user/system access includes WABA-level permissions. See [WhatsApp Business API Permissions](https://dualhook.com/docs/whatsapp-business-api-permissions) for Dualhook-specific access requirements. Migration and Portability[](https://dualhook.com/docs/whatsapp-business-accounts#migration-and-portability) ------------------------------------------------------------------------------------------------------------ Before moving numbers or changing providers: * Confirm whether the target flow supports number migration for your current account state. * Review template and webhook impacts before cutover. * Keep the same ownership context where possible to reduce approval and permission friction. Treat WABA/number migration as a controlled project, not a same-day toggle. Related ------- * [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) The top-level container for business assets in Meta and why it matters for WhatsApp. * [Phone Numbers](https://dualhook.com/docs/phone-numbers) Managing phone numbers in WhatsApp Business Platform: adding, removing, and limits. * [WABA Integration](https://dualhook.com/docs/waba-integration) End-to-end WhatsApp Business Account integration: token validation, discovery, and subscription. * [How to Leave Your WhatsApp BSP and Own Your WABA](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba) Migrate a WhatsApp business phone number away from a Solution Partner: what Meta preserves, what resets, the prerequisites, billing during migration, and the Coexistence caveat. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Leave a WhatsApp BSP & Own Your WABA (2026) | Dualhook Browse Docs How to Leave Your WhatsApp BSP and Own Your WABA ================================================ Migrate a WhatsApp business phone number away from a Solution Partner: what Meta preserves, what resets, the prerequisites, billing during migration, and the Coexistence caveat. Looking for the high-level overview first? Start with [Dualhook vs BSPs](https://dualhook.com/compare/dualhook-vs-bsp) and then return here for the implementation details. A vendor-neutral guide to migrating a WhatsApp business phone number away from a Business Solution Provider: what Meta preserves, what it does not, the prerequisites that block the flow, who pays for messages mid-migration, and how to decide whether owning your own WhatsApp Business Account is actually the right call. _Verified against Meta's WhatsApp Business Platform documentation on 14 August 2026._ Direct Answer[](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba#direct-answer) ------------------------------------------------------------------------------------------ **You can move your WhatsApp business phone number away from a Business Solution Provider — now called a Solution Partner — without changing the number and without losing your display name, quality rating, messaging limits, Official Business Account status, or eligible approved high-quality templates.** Meta migrates the number between WhatsApp Business Accounts, normally through [Embedded Signup](https://dualhook.com/docs/whatsapp-embedded-signup-guide) run by the new provider. The prerequisites that trip people up are: two-step verification must be disabled on the number, the display name must be approved with no pending change request, the WABA must be approved with a valid payment method attached, and the Meta Business Account must be verified. Once you own your WABA and connect through a provider that does not extend a Solution Partner credit line, you attach your own payment method and Meta bills its messaging charges directly to you. A software provider can still charge a separate usage-based platform fee, so verify both Meta's invoice and the provider's price sheet. Why Businesses Leave a BSP[](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba#why-businesses-leave-a-bsp) -------------------------------------------------------------------------------------------------------------------- Two reasons dominate, and they are genuinely different problems with different solutions. **Cost.** A Solution Partner can extend a line of credit and directly invoice clients for WhatsApp Business Platform services. A provider acting only as a Tech Provider or Tech Partner does not extend that Solution Partner credit line: the client attaches its own payment method and Meta bills its messaging charges directly. This removes a reseller from Meta's billing path, but it does not prevent the software provider from charging a separate platform or usage fee. Compare both layers. **Data exposure and control.** A traditional BSP or inbox platform usually sits in the message path, because that is what makes an inbox, a broadcast tool, or a CRM sync possible. Every message you send and receive transits — and is often stored on — the vendor's systems. If your objection is "we do not want a third party holding our customer conversations," the fix is not a cheaper BSP, it is an architecture where message-path webhooks go from Meta to your own endpoint. See [WhatsApp API Privacy](https://dualhook.com/whatsapp-api-privacy) and [GDPR and EU Compliance](https://dualhook.com/whatsapp-gdpr-eu) . There is a third, quieter reason: **lock-in anxiety**. Businesses discover mid-contract that they do not own the WABA their number lives on, and that a routine action — disabling two-step verification — requires the cooperation of the vendor they are trying to leave. Leaving is a legitimate, Meta-supported operation. Nothing below is a workaround. What You Keep[](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba#what-you-keep) ------------------------------------------------------------------------------------------ From Meta's Solution Partner migration documentation, verbatim: > Clients can migrate their business phone numbers between WhatsApp Business Accounts (WABAs) and retain their display names, quality ratings, template messaging limits, Official Business Account statuses, and approved, high-quality templates. And on why: > Since the client's business phone number is not changing, its display name, quality rating, messaging limit, and Official Business Account status are all preserved. On templates: > Templates are automatically duplicated in the destination WABA and initially granted the same status as their source counterparts. After duplication however, templates are re-checked to ensure they are correctly categorized according to our guidelines. This may result in some duplicated templates having their status set to REJECTED. Only templates with both a status of APPROVED and quality\_score of GREEN are eligible for duplication. In practice that means the assets you spent months earning — your [display name](https://dualhook.com/docs/display-names) , your [quality rating](https://dualhook.com/docs/health-monitoring) , your [messaging tier](https://dualhook.com/docs/capacity-quality-limits) , your [OBA badge](https://dualhook.com/docs/official-business-account) — survive the move intact. What You Do Not Keep[](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba#what-you-do-not-keep) -------------------------------------------------------------------------------------------------------- This is the half most migration guides skip. **Template quality ratings.** Meta, verbatim: "Quality ratings of templates will NOT be migrated. All migrated templates will start with an UNKNOWN rating. This rating will remain for the first 24 hours, after which a new rating will be generated if sufficient data is available." **Non-approved or non-GREEN templates.** Only templates that are both `APPROVED` and `GREEN` are eligible for duplication. A YELLOW template, a paused template, a pending template, or a rejected one does not come across. Recreate and resubmit them. **Templates beyond the destination limit.** If the destination WABA hits its template limit during duplication, the remainder are simply not created. Count your templates before you start. **Duplicated templates that fail recategorization.** Meta re-checks category correctness on duplication. A utility template that was quietly miscategorized in the source WABA can arrive as `REJECTED`. See [template category updates](https://dualhook.com/docs/webhook-template-category-update) . **Message history and media.** Meta's migration page states that uploaded media can continue to be used, but it does not include chat history among the assets it promises to transfer. Treat chat-history transfer as **not provided unless your source and destination providers explicitly document it for your path**, and export anything you need before you start. This matters especially when your current BSP is also your inbox: that product's conversation archive is separate from the WABA migration. Prerequisites Checklist[](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba#prerequisites-checklist) -------------------------------------------------------------------------------------------------------------- From Meta's migration documentation. Every one of these blocks the flow if unmet: | Requirement | Who can satisfy it | | --- | --- | | The Meta Business Account has a **verified** status | You, via Meta Business Verification | | The existing WABA has a status of **approved** | Existing WABA owner | | The existing WABA has a **valid payment method** attached in Payment Settings | Whoever owns the WABA | | The business phone number has **two-step verification disabled** | You if you own the WABA; otherwise your current Solution Partner | | The business phone number has an **approved display name** (`name_status` is `APPROVED`) | You, before starting | | **No pending display name change request** on the number | You, before starting | | The number will be registered for **Cloud API** | Migrated numbers can only use Cloud API | | The number is **not a Meta test number** | Test business phone numbers cannot be migrated | Meta's first migration step is explicit: migration cannot complete until two-step verification is disabled on the number. That single line is the whole negotiation. If you do not own your WABA, you need your current provider to perform an action whose only purpose is to let you leave. Ask for it in writing, early, with a date. It is a routine request and most providers action it, but it is not instant, and it is the step that turns a two-day migration into a two-week one. See [Two-Step Verification](https://dualhook.com/docs/two-step-verification) for where the setting lives once you own the WABA. Billing During Migration[](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba#billing-during-migration) ---------------------------------------------------------------------------------------------------------------- Meta, verbatim: > Messages delivered before migration is complete are charged to the old Solution Partner. Undelivered messages sent before migration is complete will be charged to the old Solution Partner if they are delivered after migration is complete. Messages delivered after migration is complete are charged to the business customer. This is not a simple delivery-time cut. Messages delivered before completion go to the old Solution Partner, and Meta applies a carryover rule to messages **sent before completion but delivered afterwards**: those also go to the old Solution Partner. Messages sent and delivered after completion go to the business customer. Meta also notes that phone number registration happens instantly, so sending and receiving continue without interruption, while template duplication takes time. If template downtime is your concern, migrate templates first. The Coexistence Caveat[](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba#the-coexistence-caveat) ------------------------------------------------------------------------------------------------------------ If your number runs [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) — the WhatsApp Business app and Cloud API on the same number — leaving a partner is more constrained than the standard migration path. Meta's Coexistence onboarding documentation states that a business that previously worked with a partner and still shares that partner's credit line may see an error when switching to a new partner, and links to a resolution guide. Do not assume the standard WABA-to-WABA migration path applies unchanged to Coexistence. Provider guidance differs: for example, 360dialog states that Coexistence numbers cannot be migrated between WABAs and describes a provider switch as an in-app disconnect followed by Coexistence re-onboarding. That is provider-specific guidance, not a universal Meta guarantee. Confirm the source WABA, destination WABA, and credit-line state with both providers before choosing a path. In the field, the way this surfaces is Embedded Signup failing with `#2655093` — the account is still shared with a partner — before the new provider is granted any access at all. That code is widely reported and is documented at [Error 2655093](https://dualhook.com/docs/whatsapp-error-2655093) , though it does not appear in Meta's public Cloud API error tables. The business can disconnect the current Business Platform relationship in the WhatsApp Business app; the previous provider may also need to complete backend offboarding, and Meta support may be needed for a stale relationship. The new provider cannot inspect or remove it before Meta grants access. The practical consequence: if you are on Coexistence, get written confirmation of full backend offboarding from your current provider, not just "we removed you from our dashboard." Those are different operations and only one of them unblocks you. Migration Runbook[](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba#migration-runbook) -------------------------------------------------------------------------------------------------- 1. **Confirm the prerequisites.** Meta Business Account verified, WABA approved, valid payment method attached, display name approved with no pending change request. 2. **Get two-step verification disabled.** Do it yourself in WhatsApp Manager if you own the WABA. Otherwise request it from your current Solution Partner in writing, with a target date. 3. **Export everything you want to keep.** Message history, media, template content, contact lists, and any reporting that lives only in your current vendor's dashboard. Meta does not promise chat-history transfer in its migration documentation. 4. **Inventory your templates.** Note which are `APPROVED` and `GREEN` — only those duplicate. Everything else is a recreate-and-resubmit task on the far side. 5. **Complete Embedded Signup with the new provider.** Depending on the migration path, create or select the destination WABA under your business portfolio, then migrate the number. Registration is instant, so messaging continues. 6. **Re-verify what carried over.** Display name, quality rating, messaging limit, and OBA status should be intact. Template quality ratings will read `UNKNOWN` for roughly the first 24 hours. 7. **Reconfigure webhooks.** If you want message-path webhooks delivered to your own endpoint rather than a vendor's, set the WABA-level [Webhook Override](https://dualhook.com/docs/webhook-override) at this step. Your endpoint must already answer Meta's `hub.challenge` verification. 8. **Recreate the templates that did not duplicate.** Non-approved, non-GREEN, over-limit, or recategorized-to-rejected. 9. **Reconcile the final invoice.** Messages delivered before completion, plus messages sent before completion but delivered later, belong to the old partner. Messages sent and delivered after completion belong to the business customer. Decision Framework: BSP vs Owning Your WABA[](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba#decision-framework-bsp-vs-owning-your-waba) ----------------------------------------------------------------------------------------------------------------------------------------------------- | Question | Lean toward a Solution Partner / BSP | Lean toward owning your WABA | | --- | --- | --- | | Do you need a Meta line of credit because you cannot attach your own payment method? | Yes | No | | Do you want a turnkey inbox, CRM, or campaign UI included? | Yes | No, you run your own backend or a separate tool | | Do you accept a provider reselling Meta's messaging charges? | Yes | No, you want Meta to bill its charges directly | | Does message content need to stay out of a vendor's store? | Not a priority | Priority | | Do you have engineering capacity, in-house or partnered? | Limited | Available | | Are you sending high volume where a few cents per message compounds? | Volume is modest | Yes | | What support and escalation path do you need? | Confirm the BSP's included support | Confirm the Tech Partner's scope and Meta support available to your business | There is no universally correct answer here. A ten-person company with no developers and 2,000 messages a month may be well served by a BSP inbox. A company sending 500,000 utility templates a month, with a backend team and a compliance requirement, should compare the total provider fee and architecture rather than paying for a bundled UI it does not use. What Changes Operationally After You Leave[](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba#what-changes-operationally-after-you-leave) ---------------------------------------------------------------------------------------------------------------------------------------------------- Owning your WABA moves work onto your side of the line. Plan for it: * **Billing.** You attach a payment method to your own WABA and Meta invoices you directly. Watch for [error 131042](https://dualhook.com/docs/whatsapp-error-131042) if payment setup is incomplete. * **Quality and limits.** [Account health](https://dualhook.com/docs/account-health) is yours to monitor. No one else gets the alert first. * **Template lifecycle.** Approvals, rejections, quality drops, and category changes arrive as [webhooks](https://dualhook.com/docs/webhook-message-template-status-update) you need to handle or a dashboard you need to watch. * **Webhook delivery.** Your endpoint's uptime is now part of your messaging reliability. Meta retries, but not forever. * **Support.** Support scope and Meta escalation access vary by provider and partner status. Confirm ownership for onboarding, account restrictions, billing, and migration cases before you leave. * **Pricing changes.** They land on your invoice at Meta's rates. See [WhatsApp Pricing Changes 2026](https://dualhook.com/docs/whatsapp-pricing-changes-2026) for what is scheduled. None of that is hard. It is just no longer someone else's problem. Where Dualhook Fits[](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba#where-dualhook-fits) ------------------------------------------------------------------------------------------------------ Dualhook is a badged Meta Tech Partner, not a BSP. Customers keep their own Meta Business assets and their own billing relationship with Meta, and Dualhook charges a flat platform fee with no per-message component. Dualhook configures the connection through Embedded Signup and sets a WABA-level Webhook Override so message-path webhooks route directly from Meta to your endpoint, and it does not store message content. If you are leaving a BSP specifically to stop paying per-message markup and to keep customer conversations out of a vendor's store, that is the model Dualhook is built for. If what you actually want is a hosted inbox with agents, tags, and campaign tooling, a BSP is a better fit and you should stay with one. See [Dualhook vs BSPs](https://dualhook.com/compare/dualhook-vs-bsp) , [Tech Partner](https://dualhook.com/tech-partner) , and [Pricing](https://dualhook.com/pricing) . _Operational, not legal, advice. If GDPR, data residency, or your current contract's termination and data-return clauses matter to your situation, take your own legal advice and read the contract before you start._ FAQ[](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba#faq) ---------------------------------------------------------------------- **Will I lose my WhatsApp number when I leave my BSP?** No. Migration moves the number between WhatsApp Business Accounts. The number itself does not change, and Meta registers it on the destination WABA instantly so messaging continues. **Will my quality rating and messaging limits reset?** No. Meta preserves the phone number's display name, quality rating, messaging limit, and Official Business Account status, because the number itself is not changing. Template quality ratings are the exception: they reset to UNKNOWN for the first 24 hours after duplication, then regenerate if there is enough data. **Do my templates come with me?** Only templates that are both APPROVED and GREEN are duplicated into the destination WABA. Meta re-checks category correctness after duplication, so some can arrive REJECTED. Anything not approved, not GREEN, or over the destination WABA's template limit must be recreated and resubmitted. **Do I need my current provider's cooperation to leave?** For at least one step, usually yes. Two-step verification must be disabled on the number before migration can complete. If you own the WABA you can do that yourself in WhatsApp Manager; if your Solution Partner owns it, only they can. **Is there downtime during migration?** Phone number registration is instant, so sending and receiving continue. Template duplication takes time, so migrate templates first if template downtime matters to you. **Who pays for messages sent during the migration?** Messages delivered before migration completes are charged to the old Solution Partner. Meta also charges messages sent before completion to the old partner when they are delivered afterwards. Messages sent and delivered after completion are charged to the business customer. **Does my message history come with me?** Do not assume it does. Meta's migration documentation says uploaded media remains usable but does not promise chat-history transfer. Export anything you need before starting, especially when your BSP also provides the inbox where that history is stored. **Can I migrate a Coexistence number to a new provider?** It is more constrained than a standard migration. Meta warns that a prior shared credit line can block a partner switch, and provider-specific WABA rules differ. Confirm the source and destination WABA path with both providers, disconnect the old Business Platform relationship as instructed, and get written confirmation of full backend offboarding. **What is the difference between a Solution Partner and a Tech Provider?** A Solution Partner (the role formerly called BSP) can extend a Meta line of credit and invoice you for WhatsApp messaging. A provider acting only as a Tech Provider or Tech Partner does not extend that credit line, so you attach your own payment method and Meta bills its messaging charges directly. The provider can still charge a separate platform or usage fee. Sources[](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba#sources) ------------------------------------------------------------------------------ * Meta, [Migrating a business phone number from one Solution Partner to another](https://developers.facebook.com/documentation/business-messaging/whatsapp/solution-providers/support/migrating-phone-numbers-among-solution-partners-via-embedded-signup) * Meta, [Migrating a client off a Multi-Partner Solution](https://developers.facebook.com/documentation/business-messaging/whatsapp/solution-providers/support/migrating-customers-off-solutions-via-embedded-signup) * Meta, [Onboarding WhatsApp Business app users](https://developers.facebook.com/documentation/business-messaging/whatsapp/embedded-signup/onboarding-business-app-users) * 360dialog, [WhatsApp Coexistence](https://docs.360dialog.com/partner/partner-hub/whatsapp-coexistence) — provider-specific migration guidance All checked 14 August 2026. Quoted passages are Meta's wording. Provider-specific Coexistence guidance and the absence of a Meta promise to transfer chat history are labelled explicitly rather than presented as universal Meta behavior. Related ------- * [WhatsApp Business Accounts](https://dualhook.com/docs/whatsapp-business-accounts) What a WABA is, how it relates to phone numbers, and how ownership works. * [WhatsApp Error 2655093: Account Already Shared With a Partner](https://dualhook.com/docs/whatsapp-error-2655093) How to safely release an old BSP or Coexistence partner relationship when Meta blocks Embedded Signup with error 2655093. * [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) How Coexistence mode works: using WhatsApp Business App and Cloud API on the same number. * [Two-Step Verification (2FA)](https://dualhook.com/docs/two-step-verification) Phone-number-level security using a 6-digit PIN for WhatsApp Business Platform. * [WhatsApp Embedded Signup Guide](https://dualhook.com/docs/whatsapp-embedded-signup-guide) The vendor-neutral implementer's guide to Meta's Embedded Signup flow: prerequisites, the exact client and server calls, Coexistence, v4, and what breaks in production. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Runtime API | Dualhook Browse Docs Runtime API =========== Use, protect, rotate, and scope dh\_live\_ keys for Dualhook's allowlisted Graph-compatible WhatsApp runtime. Graph-Compatible Runtime[](https://dualhook.com/docs/runtime-api#graph-compatible-runtime) ------------------------------------------------------------------------------------------- The Runtime API is a deliberately allowlisted subset of Meta's WhatsApp Cloud API and WhatsApp Business Management API. For supported operations, migrate an existing Graph request by changing: - https://graph.facebook.com/v25.0/... + https://api.dualhook.com/v25.0/... - Authorization: Bearer + Authorization: Bearer Keep the supported Meta path, method, query, and JSON shape. Dualhook is not an unrestricted Graph API proxy: only the operations in the [Runtime API Endpoint Reference](https://dualhook.com/docs/runtime-api-reference) are accepted. When a request fails, [Runtime API Errors](https://dualhook.com/docs/runtime-api-errors) works back from the response to the fix. Connection-Scoped Keys[](https://dualhook.com/docs/runtime-api#connection-scoped-keys) --------------------------------------------------------------------------------------- Each `dh_live_...` key is bound to exactly one Dualhook connection, organization, phone-number ID, and WABA ID. It cannot be used for another connection or as a general Meta credential. Create and Store a Key[](https://dualhook.com/docs/runtime-api#create-and-store-a-key) --------------------------------------------------------------------------------------- An organization admin creates the key from **Connection → Overview → Outbound API key**. Plaintext is displayed once. Dualhook stores only its SHA-256 hash. Store the key in a server-side secret manager. Never put it in browser code, mobile applications, public automation exports, logs, screenshots, or support messages. Do not share, resell, sublicense, or reuse it for another customer. Authorization: Bearer dh_live_... Authentication Boundary[](https://dualhook.com/docs/runtime-api#authentication-boundary) ----------------------------------------------------------------------------------------- Customers submit only the `dh_live_...` credential. Never submit any of these values to the Runtime API: * Meta access or app access tokens * Meta App Secret or app ID * `appsecret_proof` * a second `Authorization` value * business, WABA, or phone IDs in a request body * Graph hosts or webhook/callback URLs Dualhook loads the selected connection's encrypted Meta authorization inside the Worker and applies Meta App Secret Proof internally to every authenticated Graph request. Caller credentials, proof, hosts, or asset overrides are rejected rather than forwarded. Temporary media-download URLs are the documented exception: after a proof-aware media metadata request, the Worker downloads bytes from a validated Facebook/CDN hostname using Meta's Bearer-only media-download flow. Redirects remain manual. Rotation[](https://dualhook.com/docs/runtime-api#rotation) ----------------------------------------------------------- Rotation creates a new primary key and offers three choices for the old key: * revoke immediately * keep a one-hour grace overlap * keep a 24-hour grace overlap Use an overlap only long enough to deploy the new key. Routing Holds and Revocation[](https://dualhook.com/docs/runtime-api#routing-holds-and-revocation) --------------------------------------------------------------------------------------------------- A reversible billing, plan-limit, administrator, or Meta routing hold does not revoke the connection's runtime key. The key remains bound to the same connection but cannot route requests while the connection is blocked. If the final hold is released and Meta webhook routing is restored and verified, the same key works again. Keys are invalidated by terminal connection removal, explicit key revocation or rotation, expiration of a grace key, or an administrator quarantining that credential after a security incident. Creating or rotating a key is unavailable while its connection is not routable. connection\_not\_routable[](https://dualhook.com/docs/runtime-api#connection_not_routable) ------------------------------------------------------------------------------------------- A valid key receives HTTP `403` when its connection cannot currently route requests: { "error": { "code": "connection_not_routable", "reason": "billing_unpaid", "message": "The subscription does not currently permit API routing.", "docs": "https://dualhook.com/docs/runtime-api#connection_not_routable" } } The `reason` is restricted to this public allowlist: | Reason | Meaning | | --- | --- | | `onboarding_incomplete` | Connection setup is incomplete. | | `plan_limit_grace` | A temporary plan-limit grace period applies. | | `plan_limit_exceeded` | The subscription's connection limit was exceeded. | | `billing_unpaid` | The subscription does not currently permit routing. | | `subscription_canceled` | The subscription was canceled. | | `admin_hold_org` | An administrator hold applies to the organization. | | `admin_hold_connection` | An administrator hold applies to this connection. | | `meta_banned` | Meta disabled or scheduled the account for disablement. | | `meta_offboarded` | Meta reports that the account is offboarded. | | `meta_messaging_restricted` | Meta restricted messaging for the connection. | | `meta_token_invalid` | Meta authorization is invalid or expired. | | `meta_override_inactive` | Required webhook routing is inactive. | | `partner_removed` | Meta reports that the Dualhook connection was removed. | | `offboarding` | Connection removal is in progress. | | `connection_unavailable` | No more specific public reason is safely available. | Do not rotate a key in response to this error. Resolve the connection hold in Dualhook. Other authorization responses: * `401` — missing, invalid, revoked, expired, or quarantined credential. * `403 asset_scope_conflict` — the stored phone/WABA assets are also assigned to a conflicting active connection, so routing fails closed. * `403` with an object-scope message — a media or template object is owned outside the selected connection. Request Controls[](https://dualhook.com/docs/runtime-api#request-controls) --------------------------------------------------------------------------- The runtime enforces exact v25.0 routes, strict content types, explicit request schemas, field/query allowlists, bounded JSON, MIME-specific media limits, asset ownership, and manual redirects. Raw Graph pagination URLs are never accepted or returned. Use the safe `after` or `before` cursor documented in the endpoint reference; Dualhook reconstructs the Graph request internally. Every response includes `X-Dualhook-Request-Id`. Keep it in your content-free request log for support correlation. Rate Limits[](https://dualhook.com/docs/runtime-api#rate-limits) ----------------------------------------------------------------- | Bucket | Default | Scope | | --- | --- | --- | | Messages | 80 operations/second | connection | | Media upload/delete | 20 operations/minute | connection | | Media metadata/download | 120 operations/minute | connection | | Phone/WABA health and status | 6 operations/minute per connection; 30/minute per WABA | connection and WABA | | Management reads | 60 weighted units/minute | connection and WABA | | Management mutations | 10 weighted units/minute | connection and WABA | | Failed authentication | 20 rejected attempts/minute | credential | Phone and WABA reads share a six-per-minute connection allowance and a 30-per-minute WABA allowance. Successful responses are cached per connection for 60 seconds; `X-Dualhook-Cache` reports `hit` or `miss`, and cache hits include an `Age` header in seconds. Other single-object management reads cost 1 unit; template and QR lists cost 2. Profile, settings, and QR writes cost 2; a template header sample upload costs 4; template create/update costs 6; template deletion costs 10. One sample upload followed by one create therefore fits the default ten-unit mutation window. After 30 failed Meta message requests within a rolling five-minute window, sending pauses for five minutes. Successful sends do not erase failures already accumulated in that window. A throttled response uses HTTP `429` and includes `Retry-After`. Authentication is guarded separately before the database lookup. Once the database identifies a revoked or expired credential, that refusal is cached for up to 24 hours and continues returning `401`. Other rejected credentials are throttled after 20 failed attempts within a minute: further requests receive HTTP `429` with `Retry-After` for five minutes. The throttle is keyed on the credential, not on your connection or IP address, so one dead key cannot slow down your other keys, and a key that authenticates successfully never accumulates against it. If you see this, stop retrying and check that the key is still active on the connection — a key revoked by connection cleanup or by a rotation cannot be revived, and a new one must be created on the connection's Overview page. Privacy Model[](https://dualhook.com/docs/runtime-api#privacy-model) --------------------------------------------------------------------- Inbound customer-path webhooks remain direct from Meta to your endpoint. Outbound payloads and requested media transit Dualhook's Cloudflare Worker without body persistence or caching. Only successful, allowlisted phone/WABA metadata reads receive a connection-isolated 60-second response cache; no message, media, template, profile, or error response enters it. Operational logs exclude request bodies, recipients, message content, media bytes, credentials, proofs, authorization headers, SIP credentials, encryption material, and raw Meta download or pagination URLs. Daily metrics contain only connection/organization scope, operation, status, latency, byte counts, safe Meta error metadata, and — on failed requests only — a truncated SHA-256 target fingerprint plus a failure reason and pipeline stage drawn from a fixed Dualhook vocabulary. The failure reason records why the runtime itself refused a request, never customer input, object IDs, or Meta message text; successful requests store no fingerprint, reason, or stage. Related ------- * [The Basics Overview](https://dualhook.com/docs/basics-overview) Base URL, authentication, message send payloads, and webhook subscription basics. * [Messaging Overview](https://dualhook.com/docs/messaging-overview) Core messaging endpoint, text messages, formatting, delivery statuses, and common errors. * [Upload, Retrieve & Delete Media](https://dualhook.com/docs/media-upload) Media lifecycle: upload files, retrieve download URLs, and delete media objects. * [WhatsApp API Architecture & Security](https://dualhook.com/docs/architecture-security) Cloud API architecture, encryption, data residency, GDPR, and Dualhook's direct-routing security model. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Dualhook Runtime API Errors: Symptoms, Causes and Fixes | Dualhook Browse Docs Runtime API Errors ================== Troubleshoot Dualhook Runtime API failures by symptom: revoked keys, rejected media, wrong-connection scope, unsupported fields, validation, rate limits, and the send failure circuit. This page is the troubleshooting companion to the [Runtime API](https://dualhook.com/docs/runtime-api) and its [endpoint reference](https://dualhook.com/docs/runtime-api-reference) . Those pages describe the contract; this one starts from the failure you are seeing and works back to the fix. The runtime health emails Dualhook sends link straight to the sections below. How to Read a Runtime Failure[](https://dualhook.com/docs/runtime-api-errors#how-to-read-a-runtime-failure) ------------------------------------------------------------------------------------------------------------ Every Runtime API failure falls into one of two families, and the first thing to establish is which one you have. **Dualhook refused the request** before it reached Meta. The response is a small JSON envelope with a `message` and no numeric Meta `code` or `fbtrace_id`. Some refusals include a string `code`, such as `connection_not_routable` or `asset_scope_conflict`: {"error":{"message":"Credential is not valid for this media object"}} These are covered section by section on this page. Check the key, request, pace, or connection hold. A Dualhook `503` can instead indicate an internal or upstream service problem; contact support if it persists. **Meta rejected the request** after Dualhook forwarded it. The response carries Meta's own error object with a numeric `code`, and often `error_subcode`, `error_data.details`, and `fbtrace_id`: {"error":{"message":"(#132001) Template name does not exist in the translation","type":"OAuthException","code":132001,"fbtrace_id":"..."}} For these, use the numeric code. The most common ones on the runtime path are listed under [Meta Errors on the Runtime Path](https://dualhook.com/docs/runtime-api-errors#meta-errors-on-the-runtime-path) , and the full catalogue is the [Meta API Error Reference](https://dualhook.com/docs/api-errors) . Every response, success or failure, carries an `X-Dualhook-Request-Id` header. Log it next to the status; it is the fastest way for support to find the request. Revoked Key[](https://dualhook.com/docs/runtime-api-errors#revoked-key) ------------------------------------------------------------------------ **Symptom:** HTTP `401` with `{"error":{"message":"Invalid credential"}}` on every request from one integration, while the connection shows as active in the dashboard. **Cause:** the `dh_live_` key was revoked and cannot be used again. Keys are revoked when a connection is removed, when Meta removes Dualhook as a partner and cleanup runs, when you rotate and the overlap for the old key ends, or when an administrator quarantines a credential. Running Embedded Signup again after a disconnect revives the connection but does **not** revive its key. The most common shape: one server, scheduled job, or automation flow was never updated after a rotation, so most traffic succeeds and a steady trickle fails. The other shape is a fully dead integration: after a partner removal and re-onboarding, nothing succeeds until a new key is created. **Fix:** open the connection in your dashboard and create a new key from the Overview tab. The plaintext is shown once. Replace it everywhere the old key is configured, including places that are easy to forget, such as a second environment, a cron job, or an n8n, Make, or Zapier credential. There is no way to reactivate a revoked key. If you keep getting `401` with a key you just created, check for the usual transport mistakes: a truncated key, a trailing newline, a doubled `Bearer`, or a host that is still `graph.facebook.com`. See [Diagnosing a rejected key](https://dualhook.com/docs/runtime-api#authentication-boundary) . Revoked and expired keys are cached for up to 24 hours and continue returning `401`. Other authentication failures, such as an unknown or quarantined credential, trigger a five-minute `429` throttle after 20 rejections within a minute, with a `Retry-After` header. That is the same problem, not a rate-limit problem: stop retrying and fix the key. Connection Not Routable[](https://dualhook.com/docs/runtime-api-errors#connection-not-routable) ------------------------------------------------------------------------------------------------ **Symptom:** HTTP `403` with `"code":"connection_not_routable"` and a `reason` such as `billing_unpaid`, `meta_offboarded`, or `plan_limit_exceeded`. The key is valid. **Cause:** the connection is on a hold. Nothing is forwarded to Meta until the hold is released. **Fix:** do not rotate the key. Open the connection in your dashboard to see the hold and what clears it. The full list of reasons and their meaning is in [connection\_not\_routable](https://dualhook.com/docs/runtime-api#connection-not-routable) . Billing holds clear automatically once the invoice is paid; Meta-side holds clear when Meta's condition is resolved and, for a removed partner, after Embedded Signup is run again. Media Rejected[](https://dualhook.com/docs/runtime-api-errors#media-rejected) ------------------------------------------------------------------------------ **Symptom:** HTTP `403` with `Credential is not valid for this media object` on `GET /v25.0/{MEDIA_ID}` or `GET /v25.0/{MEDIA_ID}/content`, or Meta error `100` with subcode `33` on the same routes. Retrying the same ID shortly afterwards returns HTTP `429` with `This object was recently rejected by Meta as an invalid media ID`. **Cause:** Meta no longer serves that media ID. Meta documents a 7-day expiry for media IDs received in webhooks and a 30-day expiry for IDs returned by the upload API, and a media ID is only readable by the WhatsApp Business Account that received or uploaded it. Dualhook asks Meta once, and when Meta answers with a definitive denial, Dualhook remembers it briefly so a retry loop does not keep paying for the same rejected lookup. **Fix:** download inbound media promptly after the webhook arrives, store what you need, and treat a `403` on a media ID as final. If you fetch media for several phone numbers, make sure each request uses the key of the connection that received that message; a media ID from one WABA cannot be read with another connection's key. These are media **ID** lifetimes. A download URL returned by Meta has its own shorter validity; see [Meta’s media documentation](https://developers.facebook.com/documentation/business-messaging/whatsapp/business-phone-numbers/media/) . Wrong Connection[](https://dualhook.com/docs/runtime-api-errors#wrong-connection) ---------------------------------------------------------------------------------- **Symptom:** HTTP `403` with `Credential is not valid for this phone number`, `... for this WhatsApp Business Account`, `... for this template object`, or `asset_scope_conflict`. Passing a phone number ID or WABA ID to a media-only route can instead return `400`. **Cause:** each `dh_live_` key is bound to exactly one connection: one phone number ID, one WABA ID, one organization. The request named an asset that belongs to a different connection than the key that was used. `asset_scope_conflict` is the special case where the same phone number is assigned to another active connection, or the WABA is assigned to an active connection in another organization. Multiple numbers in one WABA in the same organization are allowed. **Fix:** check which key each integration uses and match it to the phone number ID or WABA ID in the request path. Multi-number setups need one key per connection, selected per request. For `asset_scope_conflict`, ask support to resolve the conflicting ownership; do not delete a working connection without checking which number and organization it serves. Unsupported Field[](https://dualhook.com/docs/runtime-api-errors#unsupported-field) ------------------------------------------------------------------------------------ **Symptom:** HTTP `400` with `Unsupported fields value`, `Unsupported query parameter: ...`, or `Unsupported credential or scope field: ...`. **Cause:** the Runtime API projects responses through a field allowlist per endpoint, and only accepts documented query parameters. Fields that work on `graph.facebook.com` directly, such as `owner_business_info` or credential-shaped fields, are rejected rather than forwarded. **Fix:** compare your `fields` value with the supported list for that endpoint in the [endpoint reference](https://dualhook.com/docs/runtime-api-reference) and drop the rest. An unsupported query response names the parameter. `Unsupported fields value` can be generic, so compare every requested field with the endpoint allowlist. Note that `?fields=health_status` drops the top-level `id`; request `?fields=id,health_status` if you need both. Invalid Request[](https://dualhook.com/docs/runtime-api-errors#invalid-request) -------------------------------------------------------------------------------- **Symptom:** HTTP `400`, `409`, `413`, or `415` with a message that names a field, a limit, or a content type, such as `Unsupported messages payload: to: Required`, `Content-Type must be application/json`, `GET requests must not include a body`, or `Media file exceeds the limit for its MIME type`. **Cause:** the request failed Dualhook's validation before it was forwarded. The runtime enforces the Cloud API schema for each operation, exact content types, bounded JSON, MIME-specific media limits, and safe pagination cursors. `409` means the operation is not available for the connection mode, for example a business profile update on a Coexistence connection. **Fix:** read the message. Schema failures name the path of the offending field. For media uploads, the `Content-Type` must be `multipart/form-data` with a boundary, the file part must be last, and the size limit depends on the MIME type; see [Input and Size Limits](https://dualhook.com/docs/runtime-api-reference#input-and-size-limits) . For `409`, use the WhatsApp Business app for that setting instead. Rate Limited[](https://dualhook.com/docs/runtime-api-errors#rate-limited) -------------------------------------------------------------------------- **Symptom:** HTTP `429` with `Rate limit exceeded` and a `Retry-After` header. **Cause:** the connection exceeded one of the per-connection buckets: 80 messages per second, 120 media reads per minute, 20 media mutations per minute, 6 phone or WABA health reads per minute, or the weighted management budgets. The limits are per connection and per operation type. **Fix:** honour `Retry-After`, and pace requests per connection rather than firing bursts. Management requests also share WABA budgets, and health reads have a separate WABA ceiling. Smoothing bursts over time helps stay within both scopes. The full table is in [Rate Limits](https://dualhook.com/docs/runtime-api#rate-limits) . Failure Circuit[](https://dualhook.com/docs/runtime-api-errors#failure-circuit) -------------------------------------------------------------------------------- **Symptom:** HTTP `429` with `Sending is temporarily paused after repeated failed Meta requests` on `POST /messages`. **Cause:** 30 or more sends from this connection failed at Meta within five minutes, so sending is paused for five minutes. A successful send in that window does not erase the failures already counted. This limits repeated failing traffic. It does not diagnose a quality-rating or spam problem. **Fix:** fix the Meta error that caused the failures first; it is the one immediately below the `429`s in your logs, and usually a template or parameter error covered in the next section. Then honour `Retry-After`. Requests refused during the pause do not extend it. More failed sends after it ends can open a new pause. Meta Errors on the Runtime Path[](https://dualhook.com/docs/runtime-api-errors#meta-errors-on-the-runtime-path) ---------------------------------------------------------------------------------------------------------------- These are the Meta codes that come back most often through the runtime. Each has its own page. | Code | Meaning | Page | | --- | --- | --- | | `100` | Invalid parameter | [Error 100](https://dualhook.com/docs/whatsapp-error-100) | | `131008` | Required parameter missing | [Error 131008](https://dualhook.com/docs/whatsapp-error-131008) | | `131009` | Parameter value not valid | [Error 131009](https://dualhook.com/docs/whatsapp-error-131009) | | `132001` | Template does not exist | [Error 132001](https://dualhook.com/docs/whatsapp-error-132001) | | `132000` | Template variable count mismatch | [Error 132000](https://dualhook.com/docs/whatsapp-error-132000) | | `132012` | Template parameter format mismatch | [Error 132012](https://dualhook.com/docs/whatsapp-error-132012) | | `131056` | Pair rate limit | [Error 131056](https://dualhook.com/docs/whatsapp-error-131056) | | `130429` | Throughput limit | [Error 130429](https://dualhook.com/docs/whatsapp-error-130429) | | `131000` | Meta internal error | Transient; retry with backoff | | `2` | Meta temporary error | Transient; retry with backoff | Two of these are not yours to fix: `131000` and `2` need retry/backoff and an upstream-status check, rather than a customer configuration change. Persistent `131000` errors need support investigation. Dualhook does not email about them. The Runtime Health Emails[](https://dualhook.com/docs/runtime-api-errors#the-runtime-health-emails) ---------------------------------------------------------------------------------------------------- When a connection keeps failing for one of the reasons above, Dualhook emails the organization's admins once per issue with the count, the affected connections, and a link to the matching section of this page. The rules: * One email per issue type, at most once every 7 days, even if the failures continue. * An issue is reported once it reaches 20 failed requests **and** 5% of the connection's traffic over the most recent 7 Europe/Brussels calendar days, including the current partial day. Thresholds are checked per connection before qualifying connections are combined into one email. Rate limiting has a higher bar of 100 requests and 10%. * At most two reports per organization are sent in one daily run. * Only actionable failures are reported. Meta’s transient errors, Dualhook internal errors, quarantined credentials, and administrative holds do not generate these reports. * A revoked key on a connection with **no** successful requests is reported as an integration outage; a revoked key alongside successful traffic is reported as a partial rotation. * If the organization's subscription is no longer active, only one email is ever sent, and only for revoked/expired keys or connection/billing holds, so a leftover integration can be switched off. A billing hold alone does not revoke the key. The emails are opt-out under **Runtime API health reports** on the Compliance page of your dashboard, and every email carries an unsubscribe link for that category. What to Send Support[](https://dualhook.com/docs/runtime-api-errors#what-to-send-support) ------------------------------------------------------------------------------------------ * The `X-Dualhook-Request-Id` from the failing response. * The timestamp, method, and path, with the phone number ID or WABA ID. * The full response body. Never send the key itself; the prefix shown in the dashboard is enough to identify it. Related ------- * [Runtime API](https://dualhook.com/docs/runtime-api) Use, protect, rotate, and scope dh\_live\_ keys for Dualhook's allowlisted Graph-compatible WhatsApp runtime. * [Runtime API Endpoint Reference](https://dualhook.com/docs/runtime-api-reference) Graph-compatible message, media, template, health, profile, phone-settings, and QR endpoints for connection-scoped dh\_live\_ keys. * [Meta API Error Reference](https://dualhook.com/docs/api-errors) Complete WhatsApp Cloud API and Meta Graph error code reference — what each code means, where it appears, and how to fix it. * [Upload, Retrieve & Delete Media](https://dualhook.com/docs/media-upload) Media lifecycle: upload files, retrieve download URLs, and delete media objects. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Runtime API Endpoint Reference | Dualhook Browse Docs Runtime API Endpoint Reference ============================== Graph-compatible message, media, template, health, profile, phone-settings, and QR endpoints for connection-scoped dh\_live\_ keys. Base URL and Compatibility[](https://dualhook.com/docs/runtime-api-reference#base-url-and-compatibility) --------------------------------------------------------------------------------------------------------- https://api.dualhook.com/v25.0 Use `Authorization: Bearer `. The supported paths intentionally match Meta Graph v25.0 so most integrations only change the host and credential. Never submit a Meta token or `appsecret_proof`; Dualhook applies both authorization and proof internally. `` and `` must exactly match the assets stored on the connection that owns the key. Object IDs are admitted only after connection/WABA ownership is proven. Endpoint Matrix[](https://dualhook.com/docs/runtime-api-reference#endpoint-matrix) ----------------------------------------------------------------------------------- | Operation | Method and path | | --- | --- | | Send message/read/typing request | `POST /v25.0//messages` | | Upload media | `POST /v25.0//media` | | Read media metadata | `GET /v25.0/` | | Download media bytes | `GET /v25.0//content` | | Delete media | `DELETE /v25.0/` | | Read phone identity and health | `GET /v25.0/` | | Read WABA identity and health | `GET /v25.0/` | | Upload a template header sample | `POST /v25.0//message_template_media` | | List/create/delete templates | `GET`, `POST`, or `DELETE /v25.0//message_templates` | | Read/update one template | `GET` or `POST /v25.0/` | | Read/update business profile | `GET` or `POST /v25.0//whatsapp_business_profile` | | Read/update safe phone settings | `GET` or `POST /v25.0//settings` | | List/create/update QR codes | `GET` or `POST /v25.0//message_qrdls` | | Read/delete QR code | `GET` or `DELETE /v25.0//message_qrdls/` | Messages[](https://dualhook.com/docs/runtime-api-reference#messages) --------------------------------------------------------------------- Dualhook validates the bounded Graph message envelope and supports Meta's current message types, including text, media, templates, reactions, contacts, locations, interactive messages, Flows, catalogs, carousels, address messages, read status, typing indicators, and BSUID-only sends using `recipient` instead of `to`. Meta-owned nested message fields pass through so newly documented message variants do not require a Dualhook release. Credential, Graph-host, and top-level asset-routing controls are always rejected. curl -X POST "https://api.dualhook.com/v25.0//messages" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "to": "12015550123", "type": "text", "text": {"body": "Hello"} }' { "messaging_product": "whatsapp", "contacts": [{"input": "12015550123", "wa_id": "12015550123"}], "messages": [{"id": "wamid..."}] } For a user whose phone number is unavailable, omit `to` and send the BSUID or parent BSUID through `recipient`: { "messaging_product": "whatsapp", "recipient_type": "individual", "recipient": "CO.2000162364208387", "type": "text", "text": {"body": "Hello"} } Every send requires `type` and either `to` or `recipient`. If both identifiers are present, Dualhook forwards both and Meta gives `to` precedence. Dualhook treats `recipient` as a bounded opaque identifier so Meta can evolve its syntax without requiring a Runtime API release, and does not store or log it. Dualhook never retries a message mutation automatically. Media[](https://dualhook.com/docs/runtime-api-reference#media) --------------------------------------------------------------- Upload uses the normal Graph multipart shape: curl -X POST "https://api.dualhook.com/v25.0//media" \ -H "Authorization: Bearer " \ -F "messaging_product=whatsapp" \ -F "file=@photo.jpg;type=image/jpeg" {"id": ""} Read metadata and bytes: curl "https://api.dualhook.com/v25.0/" \ -H "Authorization: Bearer " curl "https://api.dualhook.com/v25.0//content" \ -H "Authorization: Bearer " \ --output media.bin Metadata uses the connection's phone ID internally. The returned `url` points back to Dualhook's `/content` route; a raw temporary Meta CDN URL is never exposed. curl -X DELETE "https://api.dualhook.com/v25.0/" \ -H "Authorization: Bearer " Uploaded media is immediately bound to the connection. Inbound webhook media is adopted only after Meta verifies access with the bound phone ID. An object already owned by another connection returns `403`. Meta's optional `phone_number_id` scoping parameter is accepted on the metadata and delete routes. It must equal the connection's stored phone-number ID or the request returns `403`; Dualhook applies that ID internally either way, so the parameter can also be omitted. Phone and WABA Health[](https://dualhook.com/docs/runtime-api-reference#phone-and-waba-health) ----------------------------------------------------------------------------------------------- Use management webhooks for immediate changes. Poll this endpoint only as reconciliation, at most once every five minutes. Phone and WABA reads share a maximum of six requests per minute per connection and 30 per minute per WABA. Successful responses are cached for 60 seconds and include `X-Dualhook-Cache: hit` or `miss`. Cache hits also include the standard `Age` response header in seconds. curl "https://api.dualhook.com/v25.0/?fields=id,display_phone_number,quality_rating,status,name_status,whatsapp_business_manager_messaging_limit,health_status" \ -H "Authorization: Bearer " Phone fields are limited to: `id`, `display_phone_number`, `verified_name`, `code_verification_status`, `quality_rating`, `platform_type`, `is_on_biz_app`, `throughput`, `last_onboarded_time`, `is_official_business_account`, `account_mode`, `status`, `name_status`, `new_name_status`, `whatsapp_business_manager_messaging_limit`, and `health_status`. `webhook_configuration` is intentionally unavailable. Meta's phone `health_status` already summarizes the related phone, WABA, business, and app health. Do not make a second WABA health request when that summary is all you need. curl "https://api.dualhook.com/v25.0/?fields=id,name,message_template_namespace,account_review_status,business_verification_status,health_status" \ -H "Authorization: Bearer " WABA fields are limited to `id`, `name`, `currency`, `timezone_id`, `message_template_namespace`, `account_review_status`, `business_verification_status`, `health_status`, and `is_enabled_for_insights`. { "id": "", "quality_rating": "GREEN", "status": "CONNECTED", "name_status": "APPROVED", "whatsapp_business_manager_messaging_limit": "TIER_10K" } Message Templates[](https://dualhook.com/docs/runtime-api-reference#message-templates) --------------------------------------------------------------------------------------- List templates with an allowlisted field set: curl "https://api.dualhook.com/v25.0//message_templates?fields=id,name,language,status,category,rejected_reason,quality_score,health_status&status=APPROVED&limit=25" \ -H "Authorization: Bearer " Supported read fields include identity, language, status, current/previous/correct category, rejection reason, components, quality score and reasons, health/can-send state, parameter/display format, send TTL, update time, link-tracking choice, and primary-device delivery state. Template lists may be filtered by validated `name`, `status`, `category`, and `language` values. For an `IMAGE`, `VIDEO`, or `DOCUMENT` header, first upload one sample file. This Dualhook-owned WABA route performs Meta's two-step resumable upload internally and returns only the resulting template header handle: curl -X POST \ "https://api.dualhook.com/v25.0//message_template_media?file_length=12345&file_type=image%2Fjpeg" \ -H "Authorization: Bearer " \ -H "Content-Type: image/jpeg" \ --data-binary "@header.jpg" {"h":""} `file_length` is the exact raw file size in bytes and must match the body. `file_type` must exactly match `Content-Type`. Supported samples are JPEG/PNG (5 MiB), MP4/3GPP (16 MiB), and PDF (100 MiB). Dualhook creates the app-scoped upload session internally; callers cannot provide an app ID, upload-session ID, offset, token, proof, or Graph host. Create: curl -X POST "https://api.dualhook.com/v25.0//message_templates" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "shipping_update", "language": "en_US", "category": "UTILITY", "components": [\ {\ "type": "HEADER",\ "format": "IMAGE",\ "example": {"header_handle": [""]}\ },\ {\ "type": "BODY",\ "text": "Order {{1}} has shipped.",\ "example": {"body_text": [["A123"]]}\ }\ ] }' {"id": "", "status": "PENDING", "category": "UTILITY"} The uploaded sample is for Meta's template review. When sending the approved template through `/messages`, supply the actual per-message media as a URL or phone-scoped media ID in the template header parameter. Read and update an owned template: curl "https://api.dualhook.com/v25.0/?fields=id,name,status,quality_score,rejected_reason" \ -H "Authorization: Bearer " curl -X POST "https://api.dualhook.com/v25.0/" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"components":[{"type":"BODY","text":"Order {{1}} is on its way."}]}' Template names and languages are immutable on the update route. For the first direct read of a template that has not yet been listed through Dualhook, include at least one template-specific field such as `name`, `status`, or `language`. Dualhook uses that hint to distinguish a template ID from a media ID, verifies the template against the selected WABA, and records only minimal ownership metadata. Delete all languages with a name, one exact template with `name` plus `hsm_id`, or at most 100 verified IDs with a standalone `hsm_ids` parameter: curl -X DELETE "https://api.dualhook.com/v25.0//message_templates?name=shipping_update&hsm_id=" \ -H "Authorization: Bearer " Business Profile[](https://dualhook.com/docs/runtime-api-reference#business-profile) ------------------------------------------------------------------------------------- curl "https://api.dualhook.com/v25.0//whatsapp_business_profile?fields=about,address,description,email,profile_picture_url,websites,vertical" \ -H "Authorization: Bearer " { "data": [{\ "business_profile": {\ "about": "Support Monday through Friday",\ "email": "support@example.com",\ "websites": ["https://example.com"],\ "vertical": "PROF_SERVICES"\ }\ }] } curl -X POST "https://api.dualhook.com/v25.0//whatsapp_business_profile" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"about":"Support Monday through Friday","websites":["https://example.com"]}' Dualhook injects `messaging_product`. Updates may include `about`, `address`, `description`, `email`, `profile_picture_handle`, up to two `websites`, and a supported `vertical`. The app-scoped profile-image upload-session endpoint is not exposed. For a Coexistence connection, read access remains available but profile changes must be made in the WhatsApp Business app. Runtime profile updates return `409`. Phone Settings and Calling[](https://dualhook.com/docs/runtime-api-reference#phone-settings-and-calling) --------------------------------------------------------------------------------------------------------- curl "https://api.dualhook.com/v25.0//settings" \ -H "Authorization: Bearer " The response is projected to safe `user_identity_change` and `calling` configuration. `include_sip_credentials` is rejected, and SIP passwords, servers, keys, payload-encryption settings, and storage configuration are never returned. Each update must contain exactly one feature: curl -X POST "https://api.dualhook.com/v25.0//settings" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"user_identity_change":{"enable_identity_key_check":true}}' curl -X POST "https://api.dualhook.com/v25.0//settings" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "calling": { "status": "ENABLED", "call_icon_visibility": "DEFAULT", "callback_permission_status": "ENABLED", "audio": {"additional_codecs": ["PCMA", "PCMU"]}, "video": {"status": "DISABLED"} } }' Success: {"success": true} Live call signaling (`/calls`), SDP, and call permissions are not part of this runtime surface. Calling-setting updates are not available for Coexistence connections and return `409`; manage those settings in the WhatsApp Business app. Safe identity-change settings remain available. QR Codes[](https://dualhook.com/docs/runtime-api-reference#qr-codes) --------------------------------------------------------------------- List: curl "https://api.dualhook.com/v25.0//message_qrdls?fields=code,prefilled_message,deep_link_url&limit=25" \ -H "Authorization: Bearer " Read one code: curl "https://api.dualhook.com/v25.0//message_qrdls/" \ -H "Authorization: Bearer " Create or update (include an existing 14-character `code` to update): curl -X POST "https://api.dualhook.com/v25.0//message_qrdls" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"prefilled_message":"Tell me more","generate_qr_image":"SVG"}' { "code": "4O4YGZEG3RIVE1", "prefilled_message": "Tell me more", "deep_link_url": "https://wa.me/message/4O4YGZEG3RIVE1" } Delete: curl -X DELETE "https://api.dualhook.com/v25.0//message_qrdls/" \ -H "Authorization: Bearer " The prefilled message is limited to 140 characters. QR image format is `PNG` or `SVG`. Pagination[](https://dualhook.com/docs/runtime-api-reference#pagination) ------------------------------------------------------------------------- Template and QR list routes accept `limit` plus one safe `after` or `before` cursor. Template limits are 1–100; QR limits are 1–25. { "data": [], "paging": { "cursors": { "before": "", "after": "" } } } Dualhook strips Meta's raw `next` and `previous` URLs. Send only the cursor value back to the same Dualhook route: GET /v25.0//message_templates?limit=25&after= Input and Size Limits[](https://dualhook.com/docs/runtime-api-reference#input-and-size-limits) ----------------------------------------------------------------------------------------------- | Input | Limit | | --- | --- | | Message JSON | 64 KiB | | Template create/update JSON | 128 KiB | | Template header JPEG/PNG sample | 5 MiB | | Template header MP4/3GPP sample | 16 MiB | | Template header PDF sample | 100 MiB | | Business profile JSON | 16 KiB | | Settings JSON | 32 KiB | | QR JSON | 4 KiB | | Audio/video media | 16 MiB | | Documents | 100 MiB | | JPEG/PNG | 5 MiB | | WebP | 500 KiB | JSON routes require `Content-Type: application/json`. Media upload requires a valid multipart boundary, `messaging_product=whatsapp`, and a final file part with a supported MIME type. The optional `type` part must match the file MIME type when supplied. `Content-Length` is optional; the streamed file is still stopped at its MIME-specific limit. Template header sample upload uses the raw file body rather than multipart. Both `file_length` and `file_type` are required, `Content-Type` must match, and the streamed body must contain exactly the declared number of bytes. Errors[](https://dualhook.com/docs/runtime-api-reference#errors) ----------------------------------------------------------------- Runtime-owned errors use a stable JSON envelope. For a symptom-first walkthrough of each one, see [Runtime API Errors](https://dualhook.com/docs/runtime-api-errors) . {"error":{"message":"Unsupported query parameter: owner_business_info"}} Important statuses: * `400` invalid schema, field, cursor, query, or asset-shaped request. * `401` invalid, revoked, expired, or quarantined `dh_live_` credential. * `403 connection_not_routable` valid key, blocked connection. * `403 asset_scope_conflict` ambiguous active ownership. * `403` object ownership mismatch. * `409` operation unavailable for the connection mode, such as a Coexistence-only profile or calling setting. * `413` body or media limit exceeded. * `415` unsupported content type or MIME type. * `429` rate or failure-circuit limit, with `Retry-After`; this includes repeated authentication failures for one credential, returned in place of `401` once that credential is throttled. Cached revoked/expired credentials continue returning `401` without a database authentication lookup for up to 24 hours. * `502` invalid or oversized upstream response/redirect. * `503` runtime configuration or internal dependency unavailable. Meta error codes, safe messages, and trace IDs remain useful, but credential-like fields and values are recursively sanitized. Meta error `190` also places the existing connection routing hold. Unsupported Operations[](https://dualhook.com/docs/runtime-api-reference#unsupported-operations) ------------------------------------------------------------------------------------------------- The runtime deliberately excludes arbitrary Graph paths/versions/fields, webhook subscription or callback changes, app/business/partner/system-user/token management, other businesses' assets, registration/deregistration, phone/WABA deletion or migration, PIN and display-name changes, billing/funding, SIP credentials, encryption/storage settings, live calling, Flows management, certificates, catalogs, groups, contact-book management, marketing optimization, analytics, bulk template upsert/previews, and caller-controlled app-scoped upload sessions. Template header samples are supported only through the WABA-bound one-step route above. These operations are partner-wide, lifecycle-destructive, credential-bearing, cross-business, difficult to bind to one connection, or need a separate permission and ownership design. Related ------- * [Runtime API](https://dualhook.com/docs/runtime-api) Use, protect, rotate, and scope dh\_live\_ keys for Dualhook's allowlisted Graph-compatible WhatsApp runtime. * [Meta API Error Reference](https://dualhook.com/docs/api-errors) Complete WhatsApp Cloud API and Meta Graph error code reference — what each code means, where it appears, and how to fix it. * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. * [Business Profiles](https://dualhook.com/docs/business-profiles) Managing the public-facing WhatsApp business profile for your phone number. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Coexistence 13-Day Heartbeat Safety Margin | Dualhook Browse Docs 13-Day Heartbeat Safety Margin ============================== How Meta's approximate 14-day inactivity threshold relates to Dualhook's 13-day safety timer. Looking for the high-level overview first? Start with [WhatsApp Coexistence](https://dualhook.com/whatsapp-coexistence) and then return here for the implementation details. Requirement[](https://dualhook.com/docs/heartbeat#requirement) --------------------------------------------------------------- In [coexistence](https://dualhook.com/docs/coexistence) setups, Meta may disconnect the partner connection after approximately 14 days of primary-device inactivity (`PRIMARY_INACTIVITY`). Dualhook's 13-day heartbeat is a conservative safety threshold designed to warn before Meta's approximate window, not a literal Meta requirement. Why Dualhook Cannot Auto-Detect App Opens[](https://dualhook.com/docs/heartbeat#why-dualhook-cannot-auto-detect-app-opens) --------------------------------------------------------------------------------------------------------------------------- Because message-path webhooks are routed directly to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) , and Dualhook does not retain conversation content, Dualhook cannot infer mobile app usage from message traffic. Heartbeat acknowledgment is intentionally manual. Heartbeat States[](https://dualhook.com/docs/heartbeat#heartbeat-states) ------------------------------------------------------------------------- Dualhook computes status from the last acknowledged app-open time: * **OK** — heartbeat is current * **DUE\_SOON** — deadline approaching * **OVERDUE** — Dualhook's 13-day safety window has lapsed * **UNKNOWN** — no heartbeat has been recorded yet Reminder Behavior[](https://dualhook.com/docs/heartbeat#reminder-behavior) --------------------------------------------------------------------------- Dualhook sends reminder emails for due and overdue states. Reminder events are deduplicated — at most once per 24 hours per connection. Recommended Routine[](https://dualhook.com/docs/heartbeat#recommended-routine) ------------------------------------------------------------------------------- 1. Assign a primary and backup heartbeat owner. 2. Open the WhatsApp Business App on a weekly schedule. 3. Acknowledge heartbeat in Dualhook immediately after opening. 4. Treat `OVERDUE` as an operational incident. Related ------- * [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) How Coexistence mode works: using WhatsApp Business App and Cloud API on the same number. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Messaging Health Status](https://dualhook.com/docs/health-monitoring) Monitor phone number health: AVAILABLE, LIMITED, BLOCKED states and quality rating. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Cloud API Messaging: /messages Endpoint, Statuses and Errors | Dualhook Browse Docs Messaging Overview ================== Core messaging endpoint, text messages, formatting, delivery statuses, and common errors. The Cloud API `/messages` endpoint as you use it through Dualhook's runtime: the request shape for text messages, formatting and URL-preview rules, the sent → delivered → read status lifecycle, read receipts and typing indicators, and the error codes you will meet first. Message-path webhooks reach your server directly through [Webhook Override](https://dualhook.com/docs/webhook-override) , so this page pairs with the [messaging webhook](https://dualhook.com/docs/messaging-webhook) reference. Core Endpoint[](https://dualhook.com/docs/messaging-overview#core-endpoint) ---------------------------------------------------------------------------- The main Cloud API messaging endpoint is: POST https://api.dualhook.com/v25.0//messages Use this endpoint for text messages, [template messages](https://dualhook.com/docs/sending-template-messages) , [media messages](https://dualhook.com/docs/media-messages) , and [interactive messages](https://dualhook.com/docs/service-messages-interactive) . For the broader concept of free-form messages sent inside an open customer service window — types, CSW gating, contextual replies, media caching, TTL — see [Service Messages](https://dualhook.com/docs/service-messages) . Text Messages[](https://dualhook.com/docs/messaging-overview#text-messages) ---------------------------------------------------------------------------- Text messages are the default type for free-form conversation replies. { "messaging_product": "whatsapp", "recipient_type": "individual", "to": "12015550123", "type": "text", "text": { "preview_url": false, "body": "Hello, world" } } Response: { "messaging_product": "whatsapp", "contacts": [\ { "input": "12015550123", "wa_id": "12015550123" }\ ], "messages": [\ { "id": "wamid.HBgLMTIwMTU1NTAxMjMVAgARGBI..." }\ ] } Text Formatting[](https://dualhook.com/docs/messaging-overview#text-formatting) -------------------------------------------------------------------------------- Text messages support basic formatting: * Bold: `*text*` * Italic: `_text_` * Strikethrough: `~text~` * Monospace: triple backticks Message body maximum length: 4096 characters. URL Preview Behavior[](https://dualhook.com/docs/messaging-overview#url-preview-behavior) ------------------------------------------------------------------------------------------ URL previews are controlled using `text.preview_url`: * `false` (default) — no URL preview * `true` — preview can be generated for eligible links The preview is generated for the first URL in the text. The URL must start with `http://` or `https://`. Links to media files usually do not render rich previews. Delivery Status Lifecycle[](https://dualhook.com/docs/messaging-overview#delivery-status-lifecycle) ---------------------------------------------------------------------------------------------------- Common outbound status progression: 1. `sent` — message accepted by Meta 2. `delivered` — message delivered to recipient device 3. `read` — read receipt confirmed (if applicable) These statuses arrive asynchronously through [webhook callbacks](https://dualhook.com/docs/messaging-webhook) to your endpoint. Mark Message as Read[](https://dualhook.com/docs/messaging-overview#mark-message-as-read) ------------------------------------------------------------------------------------------ { "messaging_product": "whatsapp", "status": "read", "message_id": "wamid.HBgLMTIwMTU1NTAxMjMVAgARGBI..." } Typing Indicator[](https://dualhook.com/docs/messaging-overview#typing-indicator) ---------------------------------------------------------------------------------- Show a typing indicator while preparing a response: { "messaging_product": "whatsapp", "status": "read", "message_id": "wamid.HBgLMTIwMTU1NTAxMjMVAgARGBI...", "typing_indicator": { "type": "text" } } * Requires a valid incoming `message_id` from the user. * Only works on the most recent incoming user message. * Indicator auto-dismisses after ~25 seconds if no follow-up message is sent. * If preconditions are not met, the request may be ignored without an explicit API error. Bulk Messaging[](https://dualhook.com/docs/messaging-overview#bulk-messaging) ------------------------------------------------------------------------------ For bulk operations, use approved [templates](https://dualhook.com/docs/templates) and strict opt-in hygiene. Large-volume sends should include rate control, retries, [quality monitoring](https://dualhook.com/docs/capacity-quality-limits) , and suppression for opt-outs and non-responsive users. Common Messaging Errors[](https://dualhook.com/docs/messaging-overview#common-messaging-errors) ------------------------------------------------------------------------------------------------ | HTTP | Code | Meaning | Recommended action | | --- | --- | --- | --- | | `400` | — | Bad request | Validate JSON schema and required fields | | `401` | — | Unauthorized | Replace or rotate the `dh_live_...` key, or reauthorize the connection if Meta's underlying authorization expired | | `402` | — | Payment required | Check Meta billing setup | | `470` | — | Re-engagement needed | Send approved template instead | | `429` | `2001` | Rate limit hit | Throttle sends and retry later | | `500` | — | Internal error | Retry with backoff | Authenticate with the connection's `dh_live_...` key. For a full error reference, see [Meta API Error Reference](https://dualhook.com/docs/api-errors) . Outbound payloads transit Dualhook without persistence; inbound message events still route via [Webhook Override](https://dualhook.com/docs/webhook-override) directly from Meta to your endpoint. Related ------- * [Service Messages](https://dualhook.com/docs/service-messages) Free-form messages inside an open customer service window: types, replies, TTL, and October 2026 pricing with 1,000 free messages per number each month. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. * [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) How to send template messages via Cloud API with variables, media headers, and URL buttons. * [Media Messages](https://dualhook.com/docs/media-messages) Sending and receiving images, video, audio, documents, and stickers via Cloud API. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Template Elements | Dualhook Browse Docs Template Elements ================= Template structure: headers, body, footer, buttons, variables, and practical limits. Core Components[](https://dualhook.com/docs/template-elements#core-components) ------------------------------------------------------------------------------- [Template](https://dualhook.com/docs/templates) structures are built from: * `HEADER` (optional) — text, image, video, document, or location * `BODY` (required for most categories) — the main message text with variables * `FOOTER` (optional) — small text below the body * `BUTTONS` (optional) — quick reply, URL, phone, or copy-code actions Header Formats[](https://dualhook.com/docs/template-elements#header-formats) ----------------------------------------------------------------------------- Supported header formats: * `TEXT` — plain text, supports one variable * `IMAGE` — JPEG or PNG image * `VIDEO` — MP4 video * `DOCUMENT` — PDF or other document types * `LOCATION` — when supported by your account/version For media handling, see [Media Messages](https://dualhook.com/docs/media-messages) and [Upload, Retrieve & Delete Media](https://dualhook.com/docs/media-upload) . Variable Rules[](https://dualhook.com/docs/template-elements#variable-rules) ----------------------------------------------------------------------------- Use numbered placeholders: `{{1}}`, `{{2}}`, `{{3}}`, etc. Best practices: * Keep variable ordering stable. * Provide clear example values for review. * Avoid consecutive placeholders without context text between them. Text Formatting[](https://dualhook.com/docs/template-elements#text-formatting) ------------------------------------------------------------------------------- Marketing and utility templates support WhatsApp text formatting in the body: | Format | Syntax | Result | | --- | --- | --- | | Bold | `*text*` | **text** | | Italic | `_text_` | _text_ | | Strikethrough | `~text~` | ~text~ | | Monospace | ` ```text``` ` | `text` | Use the formatting toolbar in the template editor to apply styles. Formats can be combined (e.g. `*_bold italic_*`). Authentication templates do not support text formatting. Practical Limits[](https://dualhook.com/docs/template-elements#practical-limits) --------------------------------------------------------------------------------- | Element | Limit | | --- | --- | | Header text | Up to 60 characters | | Body text | Up to 1024 characters | | Footer text | Up to 60 characters | | Total buttons | Up to 10 (marketing & utility) | | URL buttons | Max 2 within the 10 total | | Phone buttons | Max 1 within the 10 total | | Quick reply buttons | Remainder up to 10 total | Quick reply and call-to-action buttons can be mixed in the same template. For example: 2 URL + 1 phone + 7 quick reply = 10 total. For sending templates with variables, media headers, and buttons, see [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) . Related ------- * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. * [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) How to send template messages via Cloud API with variables, media headers, and URL buttons. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp account_review_update Webhook: WABA Review Decisions | Dualhook Browse Docs account\_review\_update Webhook =============================== Field reference for WhatsApp account\_review\_update webhooks: WABA review decisions, payload shape, and Dualhook handling. The `account_review_update` field reports Meta's review decision for a WhatsApp Business Account (WABA). It is useful for account readiness and onboarding state, especially when a WABA cannot yet use APIs. What account\_review\_update Reports[](https://dualhook.com/docs/webhook-account-review-update#what-account_review_update-reports) ----------------------------------------------------------------------------------------------------------------------------------- This field reports the WABA review outcome through a single `decision` value. | Decision | Meaning | | --- | --- | | `APPROVED` | The WABA passed review and can be used with APIs. | | `REJECTED` | The WABA did not meet policy requirements and cannot be used with APIs. | | `PENDING` | Review is still pending; API use may be blocked. | | `DEFERRED` | Meta deferred the decision or needs more information. | When It Fires[](https://dualhook.com/docs/webhook-account-review-update#when-it-fires) --------------------------------------------------------------------------------------- Meta sends this webhook when a WABA is approved, rejected, pending, or deferred after review against WhatsApp policy guidelines. Payload Shape[](https://dualhook.com/docs/webhook-account-review-update#payload-shape) --------------------------------------------------------------------------------------- { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "time": 1739321024,\ "changes": [\ {\ "field": "account_review_update",\ "value": {\ "decision": "APPROVED"\ }\ }\ ]\ }\ ] } Dualhook Handling[](https://dualhook.com/docs/webhook-account-review-update#dualhook-handling) ----------------------------------------------------------------------------------------------- Dualhook subscribes to `account_review_update` as a management event. Dualhook uses it to keep account-readiness signals current, surface review outcomes in operational views, and forward the event to your configured endpoint. This field never contains message bodies, contacts, or media. Dualhook stores only the operational event metadata and delivery status needed for monitoring and auditability. Implementation Notes[](https://dualhook.com/docs/webhook-account-review-update#implementation-notes) ----------------------------------------------------------------------------------------------------- * Treat non-`APPROVED` decisions as account-readiness blockers until Meta changes the decision or the business remediates the issue. * Store the WABA ID and event timestamp for audit trails. * Do not assume `PENDING` and `DEFERRED` mean the same thing. Both block use, but `DEFERRED` usually indicates Meta needs more signal or more information. * Pair this field with `account_alerts` and `account_update` for a fuller account-health picture. Related[](https://dualhook.com/docs/webhook-account-review-update#related) --------------------------------------------------------------------------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) * [WhatsApp Business Accounts](https://dualhook.com/docs/whatsapp-business-accounts) * [Maintain Account Health](https://dualhook.com/docs/account-health) Related ------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting. * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) Management webhook events: template status, quality updates, account alerts, and forwarding. * [WhatsApp Business Accounts](https://dualhook.com/docs/whatsapp-business-accounts) What a WABA is, how it relates to phone numbers, and how ownership works. * [Maintain Account Health](https://dualhook.com/docs/account-health) Ownership hygiene, quality monitoring, opt-out governance, and incident playbook. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp business_capability_update Webhook: Limits and Capabilities | Dualhook Browse Docs business\_capability\_update Webhook ==================================== Field reference for WhatsApp business\_capability\_update webhooks: messaging limits, phone-number caps, capability changes, and Dualhook handling. The `business_capability_update` field reports capability limits on a WABA or business portfolio. It is mainly about messaging capacity and phone-number capacity. What business\_capability\_update Reports[](https://dualhook.com/docs/webhook-business-capability-update#what-business_capability_update-reports) -------------------------------------------------------------------------------------------------------------------------------------------------- This field can report: * messaging limits for the business portfolio, * maximum phone numbers allowed per business portfolio, * maximum phone numbers allowed per WABA, * capability increases or decreases after Meta review or usage changes. When It Fires[](https://dualhook.com/docs/webhook-business-capability-update#when-it-fires) -------------------------------------------------------------------------------------------- Meta sends it when a WABA is created or when business capabilities change. Capability changes can be increases or decreases, so treat this as a state update rather than only as a success signal. Payload Shape[](https://dualhook.com/docs/webhook-business-capability-update#payload-shape) -------------------------------------------------------------------------------------------- { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "time": 1739321024,\ "changes": [\ {\ "field": "business_capability_update",\ "value": {\ "max_daily_conversations_per_business": 2000,\ "max_phone_numbers_per_waba": 25\ }\ }\ ]\ }\ ] } Common fields: | Field | Notes | | --- | --- | | `max_daily_conversations_per_business` | Current business messaging limit. Meta documentation may present tier-style values; normalize before display. | | `max_daily_conversation_per_phone` | Legacy per-phone field scheduled for removal from Meta docs. Prefer business-level limits. | | `max_phone_numbers_per_business` | Business portfolio phone-number cap. | | `max_phone_numbers_per_waba` | WABA phone-number cap. | Dualhook Handling[](https://dualhook.com/docs/webhook-business-capability-update#dualhook-handling) ---------------------------------------------------------------------------------------------------- Dualhook subscribes to `business_capability_update` as a management field. It uses the event to keep capacity and limit signals current in the dashboard and forwards the management event to your endpoint. The field contains account metadata, not user messages. Dualhook stores the relevant operational metadata and forwarding status only. Implementation Notes[](https://dualhook.com/docs/webhook-business-capability-update#implementation-notes) ---------------------------------------------------------------------------------------------------------- * Treat missing fields as "unchanged or not included", not as zero. * Normalize numeric and tier-style messaging-limit values into your internal format. * Alert operators when a capability decreases, because sends, number onboarding, or customer growth may be affected. * Pair this field with `phone_number_quality_update` and `account_alerts` for capacity monitoring. Related[](https://dualhook.com/docs/webhook-business-capability-update#related) -------------------------------------------------------------------------------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) * [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) * [Phone Numbers](https://dualhook.com/docs/phone-numbers) * [Messaging Health Status](https://dualhook.com/docs/health-monitoring) Related ------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting. * [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) API rate limits, messaging limit tiers, quality rating, and scaling guidance. * [Phone Numbers](https://dualhook.com/docs/phone-numbers) Managing phone numbers in WhatsApp Business Platform: adding, removing, and limits. * [Messaging Health Status](https://dualhook.com/docs/health-monitoring) Monitor phone number health: AVAILABLE, LIMITED, BLOCKED states and quality rating. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Configure Ice Breakers & Commands | Dualhook Browse Docs Configure Ice Breakers & Commands ================================= Step-by-step setup and testing of ice breakers and commands. Prerequisites[](https://dualhook.com/docs/welcome-ice-breakers-commands-config#prerequisites) ---------------------------------------------------------------------------------------------- Before configuration: * [Phone number](https://dualhook.com/docs/phone-numbers) is active on your [WABA](https://dualhook.com/docs/whatsapp-business-accounts) * You have admin access to WhatsApp Manager for that number * Your [webhook endpoint](https://dualhook.com/docs/messaging-webhook) is reachable and returning `HTTP 200` quickly Configure in WhatsApp Manager[](https://dualhook.com/docs/welcome-ice-breakers-commands-config#configure-in-whatsapp-manager) ------------------------------------------------------------------------------------------------------------------------------ 1. Open WhatsApp Manager in Meta Business Manager. 2. Go to **Account Tools** → **Phone Numbers**. 3. Click **Settings** (gear icon) for the target phone number. 4. Under **Automations**, open **Conversational Components**. 5. Add/update **Ice Breakers** (up to 4 items). 6. Add/update **Commands** (up to 30 items). 7. Save changes. If Conversational Components are not editable, ask the Business Portfolio/WABA admin to grant the required access. Test Procedure[](https://dualhook.com/docs/welcome-ice-breakers-commands-config#test-procedure) ------------------------------------------------------------------------------------------------ After configuration, test in the WhatsApp mobile app: 1. Open chat with your business number. 2. For ice-breaker tests, clear the existing thread first: open the chat, tap the business profile, select **Clear Chat** → **Clear All Messages**. 3. Return to the empty thread. 4. Start a new interaction with the business number. Expected Test Results[](https://dualhook.com/docs/welcome-ice-breakers-commands-config#expected-test-results) -------------------------------------------------------------------------------------------------------------- ### Ice Breaker Tap[](https://dualhook.com/docs/welcome-ice-breakers-commands-config#ice-breaker-tap) Standard inbound text message with `messages[].text.body` equal to the selected prompt text. ### Command Usage[](https://dualhook.com/docs/welcome-ice-breakers-commands-config#command-usage) Standard inbound text message with `messages[].text.body` starting with `/`. Troubleshooting[](https://dualhook.com/docs/welcome-ice-breakers-commands-config#troubleshooting) -------------------------------------------------------------------------------------------------- If tests do not behave as expected: 1. Confirm the webhook verify token and inbound webhook POST payload validation are correct. 2. Confirm callback URL is reachable over HTTPS and returns `HTTP 200`. 3. Confirm no old chat thread remains when testing ice breakers. 4. Confirm command/ice-breaker limits are respected (length and count). 5. Re-open Conversational Components in WhatsApp Manager and verify the saved values. Meta no longer supports Welcome Messages or the `request_welcome` webhook. Opening a chat without sending a message does not generate an event. Related ------- * [Ice Breakers & Commands](https://dualhook.com/docs/welcome-ice-breakers-commands) Ice breakers and slash commands for WhatsApp business numbers, plus the removal of request\_welcome. (Note: for interactive buttons/lists/carousels, see Interactive Service Messages.) * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Template Analytics | Dualhook Browse Docs Template Analytics ================== Measuring template performance: sent, delivered, read, and clicked metrics. What It Covers[](https://dualhook.com/docs/template-analytics#what-it-covers) ------------------------------------------------------------------------------ [Template](https://dualhook.com/docs/templates) analytics helps measure how each template performs over time. Typical metrics include: * **Sent** — total messages sent * **Delivered** — confirmed deliveries * **Read** — confirmed read receipts * **Clicked** — CTA button clicks (for applicable templates) Metric availability and endpoint shape can vary by provider. Use the analytics endpoint exposed by your stack (Meta direct or BSP route). Example Query[](https://dualhook.com/docs/template-analytics#example-query) ---------------------------------------------------------------------------- GET /whatsapp_business_accounts//template_analytics?start=1735689600&end=1738367999&granularity=DAY Example Response[](https://dualhook.com/docs/template-analytics#example-response) ---------------------------------------------------------------------------------- { "data": [\ {\ "template_name": "order_update_v1",\ "language": "en_US",\ "category": "UTILITY",\ "sent": 1240,\ "delivered": 1220,\ "read": 980,\ "clicked": 0\ },\ {\ "template_name": "welcome_offer_q1",\ "language": "en_US",\ "category": "MARKETING",\ "sent": 9500,\ "delivered": 9102,\ "read": 6022,\ "clicked": 1184\ }\ ] } CTA Link Tracking[](https://dualhook.com/docs/template-analytics#cta-link-tracking) ------------------------------------------------------------------------------------ When URL CTA templates are used, tracking parameters can be appended to links for analytics attribution. Validate this behavior in your own reporting flow before relying on click metrics for billing or campaign decisions. For template structure details, see [Template Elements](https://dualhook.com/docs/template-elements) . Related ------- * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. * [Template Elements](https://dualhook.com/docs/template-elements) Template structure: headers, body, footer, buttons, variables, and practical limits. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Coexistence | Dualhook Browse Docs WhatsApp Coexistence ==================== How Coexistence mode works: using WhatsApp Business App and Cloud API on the same number. Looking for the high-level overview first? Start with [WhatsApp Coexistence](https://dualhook.com/whatsapp-coexistence) and then return here for the implementation details. What It Is[](https://dualhook.com/docs/coexistence#what-it-is) --------------------------------------------------------------- [WhatsApp Coexistence](https://dualhook.com/whatsapp-coexistence) allows an existing WhatsApp Business App number to be used with WhatsApp Business Platform (Cloud API) at the same time. This lets businesses keep in-app messaging workflows while also running API-based automation and integrations. Why Teams Use It[](https://dualhook.com/docs/coexistence#why-teams-use-it) --------------------------------------------------------------------------- * Keep the existing business number, history, and app usage patterns. * Add API capabilities such as webhook integrations and scale messaging workflows. * Avoid forcing operational teams to abandon the WhatsApp Business App. How Communication Works[](https://dualhook.com/docs/coexistence#how-communication-works) ----------------------------------------------------------------------------------------- Coexistence relies on mirrored delivery behavior between app and API channels: * Messages sent through API can appear in the WhatsApp Business App. * Messages sent from the WhatsApp Business App can be delivered to your webhook via `smb_message_echoes` events. In Dualhook, your webhook receives message events directly from Meta via [Webhook Override](https://dualhook.com/docs/webhook-override) . Dualhook monitors management and health events for operational visibility. Pricing Behavior[](https://dualhook.com/docs/coexistence#pricing-behavior) --------------------------------------------------------------------------- * Messages sent in the WhatsApp Business App remain free. * Messages sent via Cloud API are billed under [Meta platform pricing](https://dualhook.com/docs/pricing) . * Template and policy rules still apply for API-initiated conversations. Dualhook billing remains separate from Meta message billing. Linked Devices[](https://dualhook.com/docs/coexistence#linked-devices) ----------------------------------------------------------------------- Before onboarding, businesses can use up to four companion/linked devices on the app side. After coexistence onboarding, companion apps are unlinked and only supported companion clients should be re-linked. * WhatsApp for Windows and WearOS companion clients are not supported in this flow. * Unsupported companion clients may not generate `smb_message_echoes`. * If echoes are missing, your backend may not mirror those app-originated messages. Functional Limitations[](https://dualhook.com/docs/coexistence#functional-limitations) --------------------------------------------------------------------------------------- * Standard [business verification](https://dualhook.com/docs/standard-business-verification) is not supported for coexistence numbers. * [OBA](https://dualhook.com/docs/official-business-account) (blue badge) is not supported on coexistence numbers. * The WhatsApp Business Calling API is not available for coexistence numbers. This includes both Graph API/WebRTC calling and SIP calling integrations. Voice and video calls continue to work normally in the WhatsApp Business App, but they are not exposed through the Calling API or `calls` webhooks. * Calling health values such as `can_receive_call_sip = BLOCKED` and errors `138024` or `138025` are expected on coexistence connections. They do not affect messaging or in-app calls, and SIP cannot be enabled to clear them while the number remains in coexistence mode. * Direct migration between WABAs is not supported in typical coexistence flows. * Coexistence is not compatible with unofficial WhatsApp APIs. If verification or badge requirements are critical, evaluate [Meta Verified for Business](https://dualhook.com/docs/meta-verified-for-business) where applicable. Regional Availability[](https://dualhook.com/docs/coexistence#regional-availability) ------------------------------------------------------------------------------------- Coexistence onboarding availability is region-dependent. Check Meta's current supported/restricted region list before planning rollout. Operational Notes[](https://dualhook.com/docs/coexistence#operational-notes) ----------------------------------------------------------------------------- * Do **not** uninstall the WhatsApp Business App after coexistence setup — doing so can break the connection state. * Meta may disconnect coexistence after approximately 14 days of primary-device inactivity. Open the app regularly; Dualhook's [13-day heartbeat](https://dualhook.com/docs/heartbeat) is a conservative reminder threshold before that approximate Meta window. * Dualhook triggers Meta's separate one-time contact sync first and history sync second after Webhook Override is accepted. If Meta rejects one request, Dualhook still attempts the other in the documented order because both must be requested within 24 hours. Keep WhatsApp Business open while Meta prepares the sync. The resulting `smb_app_state_sync` and `history` payloads go directly to your endpoint, and history is only delivered if the business chose to share it during onboarding. Eligibility[](https://dualhook.com/docs/coexistence#eligibility) ----------------------------------------------------------------- Coexistence is intended for businesses already actively using the WhatsApp Business App. Newly created app accounts may not be immediately eligible for platform onboarding. * In many onboarding flows, the number is expected to have at least 7 days of active WhatsApp Business App usage. * Use the latest WhatsApp Business App version on a device that can complete QR-based signup when required. * Meta eligibility can depend on account tenure, quality, and readiness signals. Account-Selection Caveat[](https://dualhook.com/docs/coexistence#account-selection-caveat) ------------------------------------------------------------------------------------------- Choose the owning [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) carefully during onboarding. In many coexistence flows, the selected business ownership context cannot be changed after registration without rework. One-Way Onboarding[](https://dualhook.com/docs/coexistence#one-way-onboarding) ------------------------------------------------------------------------------- Coexistence onboarding is designed for numbers already registered on the WhatsApp Business App and moving into app+API coexistence. It is not the reverse path for taking an already API-only number and registering it in the app through coexistence. Disconnecting or Rolling Back[](https://dualhook.com/docs/coexistence#disconnecting-or-rolling-back) ----------------------------------------------------------------------------------------------------- A number can be disconnected from platform coexistence via app/business settings, but this should be treated as a controlled change. Before disconnecting: * Align app team and API/integration team. * Review webhook and data implications. * Pause automations that depend on platform events. Related ------- * [13-Day Heartbeat Safety Margin](https://dualhook.com/docs/heartbeat) How Meta's approximate 14-day inactivity threshold relates to Dualhook's 13-day safety timer. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Meta BSUID, Usernames & Contact Book Transition Guide](https://dualhook.com/docs/contacts-bsuid) Overview of Meta's WhatsApp BSUID rollout: usernames, contact book behavior, parent BSUIDs, API recipient support, webhook changes, and migration timeline. * [WhatsApp Embedded Signup Guide](https://dualhook.com/docs/whatsapp-embedded-signup-guide) The vendor-neutral implementer's guide to Meta's Embedded Signup flow: prerequisites, the exact client and server calls, Coexistence, v4, and what breaks in production. * [How to Leave Your WhatsApp BSP and Own Your WABA](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba) Migrate a WhatsApp business phone number away from a Solution Partner: what Meta preserves, what resets, the prerequisites, billing during migration, and the Coexistence caveat. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Dualhook API Basics: Auth, Send Payloads, and Webhook Setup | Dualhook Browse Docs The Basics Overview =================== Base URL, authentication, message send payloads, and webhook subscription basics. Base URL[](https://dualhook.com/docs/basics-overview#base-url) --------------------------------------------------------------- Use Dualhook's connection-scoped WhatsApp runtime: https://api.dualhook.com/v25.0 The path, method, query, and payload shapes intentionally match the supported WhatsApp Cloud API and WhatsApp Business Management API routes. Runtime requests use `api.dualhook.com` and a connection-scoped `dh_live_...` key. Do not send a Meta access token or `appsecret_proof` to this endpoint. Dualhook applies both internally. Authentication[](https://dualhook.com/docs/basics-overview#authentication) --------------------------------------------------------------------------- Create a `dh_live_...` key from the connection's **Overview** page. It is shown once and is bound to that connection's phone number. Authorization: Bearer dh_live_... Dualhook stores only a SHA-256 hash of the key. See [Runtime API Authentication](https://dualhook.com/docs/runtime-api) for rotation and security guidance. Send a Message[](https://dualhook.com/docs/basics-overview#send-a-message) --------------------------------------------------------------------------- curl -X POST "https://api.dualhook.com/v25.0//messages" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "messaging_product":"whatsapp", "recipient_type":"individual", "to":"12015550123", "type":"text", "text":{"body":"Hello there"} }' Safe Meta response status codes and JSON remain Graph-compatible. Dualhook never retries message sends automatically because a retry could create a duplicate message. Every response includes `X-Dualhook-Request-Id` for support correlation. Allowed Routes[](https://dualhook.com/docs/basics-overview#allowed-routes) --------------------------------------------------------------------------- The runtime is not a general Graph API gateway. Its exact allowlist covers: * message sends, read receipts, and typing indicators * media upload, metadata, byte download, and delete * phone and WABA identity, status, quality, limits, and health * message-template list, create, read, update, and delete * business-profile read and update * safe identity-change and calling configuration * QR code list, create, update, and delete See the [Runtime API Endpoint Reference](https://dualhook.com/docs/runtime-api-reference) for every method/path and field allowlist. Arbitrary Graph versions, paths, fields, methods, hosts, and query parameters are rejected. Inbound Webhooks[](https://dualhook.com/docs/basics-overview#inbound-webhooks) ------------------------------------------------------------------------------- Inbound `messages`, statuses, `history`, `smb_message_echoes`, and `smb_app_state_sync` payloads still route directly from Meta to the webhook URL configured for the connection. They do not pass through the outbound runtime. Management Operations[](https://dualhook.com/docs/basics-overview#management-operations) ----------------------------------------------------------------------------------------- Templates, health/status, profiles, safe phone/calling settings, and QR codes are available through the allowlisted runtime. Dashboard management remains available where documented. Webhook subscriptions and callback overrides, Flows, registration, PINs, number lifecycle, billing, partner assignments, SIP credentials, encryption/storage settings, and other partner-wide or cross-business operations are deliberately unavailable. Dualhook keeps the customer-scoped Meta authorization encrypted server-side and never reveals it through the dashboard or API. Related ------- * [Messaging Overview](https://dualhook.com/docs/messaging-overview) Core messaging endpoint, text messages, formatting, delivery statuses, and common errors. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp message_template_components_update Webhook Reference | Dualhook Browse Docs message\_template\_components\_update Webhook ============================================= Field reference for WhatsApp message\_template\_components\_update webhooks: template component edits, button fields, payload shape, and Dualhook handling. The `message_template_components_update` field reports that an existing template's components changed. It is useful when your app caches template body text, headers, footers, or button definitions. What message\_template\_components\_update Reports[](https://dualhook.com/docs/webhook-message-template-components-update#what-message_template_components_update-reports) --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- This field can include: * template ID, name, and language, * body text, * text header, * footer, * button definitions such as URL and phone-number buttons. It does not replace the need to fetch the template from Meta if your local copy must be canonical; treat it as a change signal and lightweight payload. When It Fires[](https://dualhook.com/docs/webhook-message-template-components-update#when-it-fires) ---------------------------------------------------------------------------------------------------- Meta sends this webhook when a template is edited. Payload Shape[](https://dualhook.com/docs/webhook-message-template-components-update#payload-shape) ---------------------------------------------------------------------------------------------------- { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "time": 1751250234,\ "changes": [\ {\ "field": "message_template_components_update",\ "value": {\ "message_template_id": 1315502779341834,\ "message_template_name": "order_confirmation",\ "message_template_language": "en_US",\ "message_template_title": "Your order is confirmed!",\ "message_template_element": "Thank you for your order, {{1}}.",\ "message_template_footer": "Footer text",\ "message_template_buttons": [\ {\ "message_template_button_type": "URL",\ "message_template_button_text": "View order",\ "message_template_button_url": "https://example.com/orders"\ }\ ]\ }\ }\ ]\ }\ ] } Common button types include `URL`, `PHONE_NUMBER`, `QUICK_REPLY`, `COPY_CODE`, `FLOW`, `VOICE_CALL`, catalog and order-related buttons, and other Meta-supported template button types. Dualhook Handling[](https://dualhook.com/docs/webhook-message-template-components-update#dualhook-handling) ------------------------------------------------------------------------------------------------------------ Dualhook does not currently track `message_template_components_update` as one of its normalized management event types. If Meta sends this field to Dualhook's app-level callback, it is treated as an unknown or generic management event rather than as a first-class dashboard state transition. For your own integration, subscribe your app or endpoint if you need component-change notifications. In Dualhook, template state is primarily maintained through explicit template sync actions and the template status and quality management fields. Implementation Notes[](https://dualhook.com/docs/webhook-message-template-components-update#implementation-notes) ------------------------------------------------------------------------------------------------------------------ * Use `message_template_id` plus language as the stable lookup key. * After receiving this event, fetch the template from Meta if you need the full canonical component model. * Do not assume every optional field is present. Headers, footers, and button arrays depend on the template. * Review send builders after component changes, especially when variable count or button shape changes. Related[](https://dualhook.com/docs/webhook-message-template-components-update#related) ---------------------------------------------------------------------------------------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) * [Templates](https://dualhook.com/docs/templates) * [Template Elements](https://dualhook.com/docs/template-elements) * [message\_template\_status\_update webhook](https://dualhook.com/docs/webhook-message-template-status-update) * [message\_template\_quality\_update webhook](https://dualhook.com/docs/webhook-message-template-quality-update) Related ------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting. * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. * [Template Elements](https://dualhook.com/docs/template-elements) Template structure: headers, body, footer, buttons, variables, and practical limits. * [message\_template\_status\_update Webhook](https://dualhook.com/docs/webhook-message-template-status-update) Field reference for WhatsApp message\_template\_status\_update webhooks: template approval, rejection, pause, disablement, archive, deletion, appeal, and Dualhook handling. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp security Webhook: Two-Step Verification PIN Events | Dualhook Browse Docs security Webhook ================ Field reference for WhatsApp security webhooks: two-step verification PIN changes, reset requests, requester IDs, payload shape, and Dualhook handling. The `security` field reports security setting changes on a WhatsApp business phone number, especially two-step verification PIN changes and reset requests. What security Reports[](https://dualhook.com/docs/webhook-security#what-security-reports) ------------------------------------------------------------------------------------------ The field reports phone-number security events: | Event | Meaning | | --- | --- | | `PIN_CHANGED` | A Meta Business Suite user changed or enabled the business phone number PIN in WhatsApp Manager. | | `PIN_RESET_REQUEST` | A Meta Business Suite user requested to turn off two-step verification. | | `PIN_REQUEST_SUCCESS` | The user completed the reset-email flow to turn off two-step verification. | `requester` is included for PIN reset requests and identifies the Meta Business Suite user ID that initiated the action. When It Fires[](https://dualhook.com/docs/webhook-security#when-it-fires) -------------------------------------------------------------------------- Meta sends this field when a user changes or enables the PIN, starts a two-step verification reset, or completes the reset instructions from the reset email. Payload Shape[](https://dualhook.com/docs/webhook-security#payload-shape) -------------------------------------------------------------------------- { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "time": 1748811473,\ "changes": [\ {\ "field": "security",\ "value": {\ "display_phone_number": "15550783881",\ "event": "PIN_RESET_REQUEST",\ "requester": "61555822107539"\ }\ }\ ]\ }\ ] } Dualhook Handling[](https://dualhook.com/docs/webhook-security#dualhook-handling) ---------------------------------------------------------------------------------- Dualhook subscribes to `security` as a normalized management field. It uses the event for operational visibility around phone-number security, logs the management event metadata, and forwards it to your configured endpoint with Dualhook forwarding headers. This field does not contain message content. It can still be security-sensitive because it describes PIN reset activity, so treat it as an audit event. Implementation Notes[](https://dualhook.com/docs/webhook-security#implementation-notes) ---------------------------------------------------------------------------------------- * Alert on `PIN_RESET_REQUEST`; it can indicate an expected admin action or an unexpected security change. * Store `display_phone_number`, WABA ID, timestamp, event, and `requester` when present. * Correlate security events with staff access changes in Meta Business Suite. * If two-step verification prevents registration or recovery, see the dedicated 2FA documentation before retrying. Related[](https://dualhook.com/docs/webhook-security#related) -------------------------------------------------------------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) * [Two-Step Verification (2FA)](https://dualhook.com/docs/two-step-verification) * [Phone Numbers](https://dualhook.com/docs/phone-numbers) * [Maintain Account Health](https://dualhook.com/docs/account-health) Related ------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting. * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) Management webhook events: template status, quality updates, account alerts, and forwarding. * [Two-Step Verification (2FA)](https://dualhook.com/docs/two-step-verification) Phone-number-level security using a 6-digit PIN for WhatsApp Business Platform. * [Phone Numbers](https://dualhook.com/docs/phone-numbers) Managing phone numbers in WhatsApp Business Platform: adding, removing, and limits. * [Maintain Account Health](https://dualhook.com/docs/account-health) Ownership hygiene, quality monitoring, opt-out governance, and incident playbook. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp message_template_quality_update Webhook: Template Quality | Dualhook Browse Docs message\_template\_quality\_update Webhook ========================================== Field reference for WhatsApp message\_template\_quality\_update webhooks: template quality score changes, payload shape, and Dualhook handling. The `message_template_quality_update` field reports changes to a template's quality score. It helps you react before a template is paused or disabled due to poor feedback. What message\_template\_quality\_update Reports[](https://dualhook.com/docs/webhook-message-template-quality-update#what-message_template_quality_update-reports) ------------------------------------------------------------------------------------------------------------------------------------------------------------------ The payload identifies the template and includes the previous and new quality score. Quality values: | Score | Meaning | | --- | --- | | `GREEN` | High quality. | | `YELLOW` | Medium quality or warning state. | | `RED` | Low quality and higher risk. | | `UNKNOWN` | Quality is pending or not enough signal exists. | When It Fires[](https://dualhook.com/docs/webhook-message-template-quality-update#when-it-fires) ------------------------------------------------------------------------------------------------- Meta sends this webhook when a template's quality score changes. Payload Shape[](https://dualhook.com/docs/webhook-message-template-quality-update#payload-shape) ------------------------------------------------------------------------------------------------- { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "time": 1674864290,\ "changes": [\ {\ "field": "message_template_quality_update",\ "value": {\ "previous_quality_score": "GREEN",\ "new_quality_score": "YELLOW",\ "message_template_id": 806312974732579,\ "message_template_name": "welcome_template",\ "message_template_language": "en-US"\ }\ }\ ]\ }\ ] } Dualhook Handling[](https://dualhook.com/docs/webhook-message-template-quality-update#dualhook-handling) --------------------------------------------------------------------------------------------------------- Dualhook subscribes to `message_template_quality_update` as a management field. It uses the event to keep template quality metadata current, surface warnings in the dashboard, and forward the management event to your endpoint. The event contains template metadata and quality state only. Dualhook stores operational metadata and forwarding status; it does not store customer message content. Implementation Notes[](https://dualhook.com/docs/webhook-message-template-quality-update#implementation-notes) --------------------------------------------------------------------------------------------------------------- * Treat `YELLOW` as an early warning and `RED` as urgent. * Segment or pause campaigns that are driving negative feedback before Meta pauses the template. * Store both previous and new score so you can detect recovery as well as degradation. * Join by `message_template_id`, name, and language. Template names can be reused across languages. Related[](https://dualhook.com/docs/webhook-message-template-quality-update#related) ------------------------------------------------------------------------------------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) * [Templates](https://dualhook.com/docs/templates) * [Template Analytics](https://dualhook.com/docs/template-analytics) * [message\_template\_status\_update webhook](https://dualhook.com/docs/webhook-message-template-status-update) Related ------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting. * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) Management webhook events: template status, quality updates, account alerts, and forwarding. * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. * [Template Analytics](https://dualhook.com/docs/template-analytics) Measuring template performance: sent, delivered, read, and clicked metrics. * [message\_template\_status\_update Webhook](https://dualhook.com/docs/webhook-message-template-status-update) Field reference for WhatsApp message\_template\_status\_update webhooks: template approval, rejection, pause, disablement, archive, deletion, appeal, and Dualhook handling. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp template_category_update Webhook: Template Recategorization | Dualhook Browse Docs template\_category\_update Webhook ================================== Field reference for WhatsApp template\_category\_update webhooks: scheduled and completed template recategorization, payload shape, and Dualhook handling. The `template_category_update` field reports category reclassification for WhatsApp message templates. It is important because category changes can affect pricing, deliverability rules, and whether a template is allowed for a given use case. What template\_category\_update Reports[](https://dualhook.com/docs/webhook-template-category-update#what-template_category_update-reports) -------------------------------------------------------------------------------------------------------------------------------------------- Meta can send this field before a category change takes effect, or after the category has changed. Typical cases: * a template currently categorized as Utility will be recategorized to Marketing, * a manual or automated review has changed the template category, * Meta gives advance notice of an automated category correction. When It Fires[](https://dualhook.com/docs/webhook-template-category-update#when-it-fires) ------------------------------------------------------------------------------------------ Meta sends this webhook when an existing template category is scheduled to change or has changed. Payload Shape[](https://dualhook.com/docs/webhook-template-category-update#payload-shape) ------------------------------------------------------------------------------------------ { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "time": 1746169200,\ "changes": [\ {\ "field": "template_category_update",\ "value": {\ "message_template_id": 278077987957091,\ "message_template_name": "welcome_template",\ "message_template_language": "en-US",\ "previous_category": "UTILITY",\ "new_category": "MARKETING"\ }\ }\ ]\ }\ ] } Scheduled vs Completed Changes[](https://dualhook.com/docs/webhook-template-category-update#scheduled-vs-completed-changes) ---------------------------------------------------------------------------------------------------------------------------- Scheduled category-change notifications include: | Field | Meaning | | --- | --- | | `correct_category` | The category Meta plans to apply. | | `new_category` | The current category at the time of the scheduled-change notice. | | `category_update_timestamp` | When Meta plans to apply the corrected category. | Completed category-change notifications include: | Field | Meaning | | --- | --- | | `previous_category` | Category before the change. | | `new_category` | Category after the change. | Do not assume `new_category` always means the final category in scheduled-change payloads. In that shape, Meta's docs use `new_category` for the current category and `correct_category` for the planned category. Dualhook Handling[](https://dualhook.com/docs/webhook-template-category-update#dualhook-handling) -------------------------------------------------------------------------------------------------- Dualhook subscribes to `template_category_update` as a normalized management field. It uses category updates to keep template metadata aligned with Meta, surfaces category changes in operational views, and forwards the management event to your configured endpoint. This field contains template metadata only. Dualhook stores operational metadata and delivery status; it does not store message bodies. Implementation Notes[](https://dualhook.com/docs/webhook-template-category-update#implementation-notes) -------------------------------------------------------------------------------------------------------- * Treat scheduled and completed payloads as different shapes. * If a Utility template is moving to Marketing, review pricing, opt-in, and marketing frequency-cap behavior before continuing sends. * Join by template ID and language; names can be reused across languages. * Keep the scheduled timestamp so operators can adjust campaigns before the category change lands. * If the category looks wrong, fix the template category in Meta's template tooling rather than ignoring the webhook. Related[](https://dualhook.com/docs/webhook-template-category-update#related) ------------------------------------------------------------------------------ * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) * [Templates](https://dualhook.com/docs/templates) * [Template Elements](https://dualhook.com/docs/template-elements) * [Marketing Messages](https://dualhook.com/docs/marketing-messages) * [Utility Messages](https://dualhook.com/docs/utility-messages) Related ------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting. * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) Management webhook events: template status, quality updates, account alerts, and forwarding. * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. * [Marketing Messages](https://dualhook.com/docs/marketing-messages) Marketing template messaging through Dualhook's standard Cloud API runtime, plus boundaries for Meta's separate MM API product. * [Utility Messages](https://dualhook.com/docs/utility-messages) Utility template messaging hub: components and buttons, specialty types, the October 2026 CSW billing change, volume tiers, content rules, and recategorization. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 190: Access Token Expired Fix | Dualhook Browse Docs WhatsApp/Graph Error 190: Access Token Expired ============================================== WhatsApp/Graph error 190 means your access token expired or is invalid. Why it happens and how to switch to a long-lived system-user token. > **TL;DR:** Error `190` means the connection's underlying Meta authorization expired or was invalidated (HTTP 401). Reauthorize the connection through Embedded Signup. Dualhook stores customer-scoped BISU tokens encrypted and does not reveal or refresh them with the ordinary System User flow. What Error 190 Means[](https://dualhook.com/docs/whatsapp-error-190#what-error-190-means) ------------------------------------------------------------------------------------------ Meta's `details` string is: **"Your access token has expired."** A common variant is "Error validating access token: Session has expired…". In practice `190` covers authorization that is expired, invalid, or no longer valid for the target asset. Dualhook records the expiry metadata Meta returns and requires reauthorization if an existing BISU token expires; it does not apply the ordinary System User refresh diagram to BISU tokens. Where You See It[](https://dualhook.com/docs/whatsapp-error-190#where-you-see-it) ---------------------------------------------------------------------------------- An **immediate API error** (HTTP 401) on any Graph/Cloud API call — message sends, WABA queries, template APIs, subscription/config APIs. It blocks the request, so it is **not** delivered via the message status webhook. Common Causes[](https://dualhook.com/docs/whatsapp-error-190#common-causes) ---------------------------------------------------------------------------- * A short-lived/temporary quickstart token used in production. * The token's session changed or the token was revoked. * App or business-asset permissions were removed. * The live backend points at a different business asset than the token can access. How to Fix It[](https://dualhook.com/docs/whatsapp-error-190#how-to-fix-it) ---------------------------------------------------------------------------- 1. Check the connection Health tab to confirm the authorization failure. 2. Reauthorize the existing connection through Embedded Signup. 3. Confirm the refreshed connection has access to the correct WABA and phone number. 4. Create or rotate the connection's `dh_live_...` key if your sending backend needs a new Dualhook credential, then re-test the failing call. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-190#how-to-prevent-it) ------------------------------------------------------------------------------------ * Use the customer-scoped BISU authorization created through Embedded Signup, never a console temporary token, in production. * Track any expiry Meta reports and reauthorize before it is reached. * Re-validate asset assignments after business/admin changes. Related Errors[](https://dualhook.com/docs/whatsapp-error-190#related-errors) ------------------------------------------------------------------------------ * [`/docs/whatsapp-error-131031`](https://dualhook.com/docs/whatsapp-error-131031) — account locked * [`/docs/whatsapp-error-100-webhook-override`](https://dualhook.com/docs/whatsapp-error-100-webhook-override) — subscribe before override * [`/docs/whatsapp-error-80007`](https://dualhook.com/docs/whatsapp-error-80007) — WABA rate limit * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-190#faq) -------------------------------------------------------- **What is WhatsApp error 190?** Your access token expired or was invalidated; the API returns 401. **Why did the authorization expire?** An older connection may use an expiring authorization. Reauthorize it through the current Embedded Signup configuration. **Which permissions do I need?** `whatsapp_business_management` and `whatsapp_business_messaging`. **Why does it work in Meta's API Setup tool but not my backend?** The tool uses a different credential and asset context. Your backend should use its connection-bound `dh_live_...` key with `api.dualhook.com`. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-190#how-dualhook-helps) -------------------------------------------------------------------------------------- Token and authorization health is an **account/platform** concern where Dualhook genuinely helps. Because Dualhook connects your WABA via Meta Embedded Signup and holds **advanced access** to `whatsapp_business_management` and `whatsapp_business_messaging`, it can surface connection/authorization health and alert you when a WABA's authorization drops. Your sending backend authenticates to Dualhook with a connection-bound `dh_live_...` key; the underlying Meta token stays encrypted on Dualhook's server. See [Runtime API Authentication](https://dualhook.com/docs/runtime-api) , [Maintain Account Health](https://dualhook.com/docs/account-health) , and [Messaging Health Status](https://dualhook.com/docs/health-monitoring) . [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 131000: Something Went Wrong | Dualhook Browse Docs WhatsApp Error 131000: Something Went Wrong =========================================== WhatsApp error 131000 is Meta’s generic "something went wrong" send failure. When to retry, and how to investigate persistent unknown errors with fbtrace\_id. > **TL;DR:** WhatsApp error `131000` is Meta's generic "something went wrong" bucket for sends that failed without a more specific public code. Retry cautiously if it looks transient; if it persists, capture `fbtrace_id`, timestamps, and payloads and open support. What Error 131000 Means[](https://dualhook.com/docs/whatsapp-error-131000#what-error-131000-means) --------------------------------------------------------------------------------------------------- Meta's current title is **"Something went wrong"**; older docs and threads describe the same code as "Message failed to send due to an unknown error." The practical meaning is unchanged: the request failed without a more specific publicly-exposed root-cause code. (Title wording is a search-vocabulary note; treat the generic-failure meaning as the stable basis.) Where You See It[](https://dualhook.com/docs/whatsapp-error-131000#where-you-see-it) ------------------------------------------------------------------------------------- Both ways — an **immediate API error** and a **failed status webhook** after the send appeared accepted, which is part of why it causes confusion. Common Causes[](https://dualhook.com/docs/whatsapp-error-131000#common-causes) ------------------------------------------------------------------------------- * Temporary Meta-side processing issues or generic backend failures. * An account/template/send condition that didn't map to a more specific public code. * Cases where the send path and the final delivery path disagree. How to Fix It[](https://dualhook.com/docs/whatsapp-error-131000#how-to-fix-it) ------------------------------------------------------------------------------- 1. Determine whether the problem is broad or isolated. 2. If transient, retry with capped backoff and jitter. 3. If it persists, capture `fbtrace_id`, `wamid`, full request/response metadata, and the failed webhook payload. 4. Reproduce with a known-good recipient and template before changing unrelated code; open Meta support if it's sticky or widespread. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-131000#how-to-prevent-it) --------------------------------------------------------------------------------------- * Log every send with full Meta error metadata; build idempotent retry logic. * Separate retryable transient failures from dead-letter/manual-investigation failures. * Don't rely only on the initial API response. Related Errors[](https://dualhook.com/docs/whatsapp-error-131000#related-errors) --------------------------------------------------------------------------------- * [`/docs/whatsapp-error-131026`](https://dualhook.com/docs/whatsapp-error-131026) — undeliverable recipient * [`/docs/whatsapp-error-131031`](https://dualhook.com/docs/whatsapp-error-131031) — account locked * [`/docs/whatsapp-error-132001`](https://dualhook.com/docs/whatsapp-error-132001) — template lookup failure * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-131000#faq) ----------------------------------------------------------- **Is 131000 retryable?** Sometimes — if clearly transient. If it repeats, investigate and escalate rather than hammering retries. **Why did the title change from "unknown error" to "something went wrong"?** Meta updated the public wording; the code is the same generic failure bucket. **What should I send to Meta support?** At minimum `fbtrace_id`, `wamid`, UTC timestamp, the endpoint used, and whether the failure arrived in a webhook. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-131000#how-dualhook-helps) ----------------------------------------------------------------------------------------- This is a **send-side** symptom. Dualhook relays the outbound request without storing or body-logging it, so support cannot inspect your message body. The [API Errors](https://dualhook.com/docs/api-errors) , [Messaging Health Status](https://dualhook.com/docs/health-monitoring) , and [Maintain Account Health](https://dualhook.com/docs/account-health) references help separate a generic transient error from a real account issue. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 131048: Spam Rate Restriction | Dualhook Browse Docs WhatsApp Error 131048: Spam Rate Restriction ============================================ WhatsApp error 131048 means the phone number is restricted because previous messages were blocked or flagged as spam. How to recover number health safely. > **TL;DR:** WhatsApp error `131048` means Meta has restricted how much this phone number can send, likely because too many previous messages were blocked or reported as spam. Pause or sharply reduce risky sends, improve targeting and consent quality, and let number health recover. What Error 131048 Means[](https://dualhook.com/docs/whatsapp-error-131048#what-error-131048-means) --------------------------------------------------------------------------------------------------- Meta's `details` string is verbatim: **"Message failed to send because there are restrictions on how many messages can be sent from this phone number. This may be because too many previous messages were blocked or flagged as spam."** (Throttling error — confirmed on the live table; the short title often appears as "Spam Rate limit hit".) Meta's solution points to checking your quality status in WhatsApp Manager. This is a number-health problem, not just a per-recipient one — and it is a separate code from the per-user marketing cap `131049`. Where You See It[](https://dualhook.com/docs/whatsapp-error-131048#where-you-see-it) ------------------------------------------------------------------------------------- Most often as a **failed status/delivery error** tied to the sending number's current quality/restriction state. Common Causes[](https://dualhook.com/docs/whatsapp-error-131048#common-causes) ------------------------------------------------------------------------------- * Poor recipient targeting or weak/stale opt-ins. * Too much promotional traffic; recent spikes in blocks/reports. * Continuing volume after quality already started dropping. How to Fix It[](https://dualhook.com/docs/whatsapp-error-131048#how-to-fix-it) ------------------------------------------------------------------------------- 1. Pause or sharply reduce non-essential volume; prioritize high-value, expected traffic. 2. Review recent templates and segments for the source of negative feedback. 3. Remove risky audience cohorts first. 4. Check your quality status in WhatsApp Manager and watch account/phone health before scaling back up. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-131048#how-to-prevent-it) --------------------------------------------------------------------------------------- * Warm new numbers gradually; keep targeting tight and opt-ins fresh. * Move borderline content out of marketing where it doesn't belong. * Reduce frequency before users start blocking at scale. Use [Maintain Account Health](https://dualhook.com/docs/account-health) as standing discipline. Related Errors[](https://dualhook.com/docs/whatsapp-error-131048#related-errors) --------------------------------------------------------------------------------- * [`/docs/whatsapp-error-131049`](https://dualhook.com/docs/whatsapp-error-131049) — marketing-delivery suppression * [`/docs/whatsapp-error-131031`](https://dualhook.com/docs/whatsapp-error-131031) — account locked * [`/docs/whatsapp-error-130429`](https://dualhook.com/docs/whatsapp-error-130429) — throughput limit * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-131048#faq) ----------------------------------------------------------- **Is 131048 the same as 131049?** No — `131048` is a phone-number restriction/quality problem; `131049` is per-user marketing-delivery suppression. They are separate codes in Meta's current docs. **How long does 131048 last?** Meta doesn't publish a fixed duration — recover quality before scaling again. **Can I get 131048 even if quality looked fine recently?** Yes — block/report patterns can shift quickly. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-131048#how-dualhook-helps) ----------------------------------------------------------------------------------------- This is an **account/quality** area where Dualhook adds real value. Dualhook relays supported outbound requests without retaining their content, and surfaces [Messaging Health Status](https://dualhook.com/docs/health-monitoring) , [Maintain Account Health](https://dualhook.com/docs/account-health) , and [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) , exactly the signals you need when 131048 appears. Recovering quality and pacing still happen in your sender. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 130429: Throughput Limit Hit | Dualhook Browse Docs WhatsApp Error 130429: Throughput Limit Hit =========================================== WhatsApp error 130429 means your send rate exceeded the per-number throughput Meta allows (default ~80 msg/sec). How to queue, shard, and back off safely. > **TL;DR:** WhatsApp error `130429` means your sending rate exceeded the throughput Meta currently allows for that business phone number. Slow down, queue traffic, and spread volume; retrying hot makes it worse. What Error 130429 Means[](https://dualhook.com/docs/whatsapp-error-130429#what-error-130429-means) --------------------------------------------------------------------------------------------------- Meta's `details` string is: **"Cloud API message throughput has been reached."** (Throttling error — confirmed verbatim; Meta's own example payload uses this exact code and string, message form "(#130429) Rate limit hit".) It's the per-number send-throughput limit, distinct from recipient-pair throttling (`131056`) and WABA-management polling limits (`80007`). Where You See It[](https://dualhook.com/docs/whatsapp-error-130429#where-you-see-it) ------------------------------------------------------------------------------------- An **immediate API error** during sends when traffic to one business phone number exceeds its allowed throughput. Common Causes[](https://dualhook.com/docs/whatsapp-error-130429#common-causes) ------------------------------------------------------------------------------- * A campaign/automation sends too many messages through one number at once. * The app scales horizontally without a per-number throughput gate. * Retries during a spike amplify the spike. How to Fix It[](https://dualhook.com/docs/whatsapp-error-130429#how-to-fix-it) ------------------------------------------------------------------------------- 1. Throttle outgoing sends for the affected number; queue and drain at a controlled rate. 2. Add retry backoff with jitter. 3. If you run multiple numbers, spread traffic deliberately rather than flooding one. 4. Recheck your traffic model after the spike so it doesn't recur. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-130429#how-to-prevent-it) --------------------------------------------------------------------------------------- * Enforce throughput **per phone number**, not only per application. * Per Meta's Throughput doc, Cloud API supports **up to 80 messages per second by default** per registered business phone number, with higher levels possible via upgrade. * Pre-warm high-volume jobs instead of launching full volume on a cold sender. See [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) . Related Errors[](https://dualhook.com/docs/whatsapp-error-130429#related-errors) --------------------------------------------------------------------------------- * [`/docs/whatsapp-error-131056`](https://dualhook.com/docs/whatsapp-error-131056) — pair rate limit * [`/docs/whatsapp-error-80007`](https://dualhook.com/docs/whatsapp-error-80007) — WABA management rate limit * [`/docs/whatsapp-error-131048`](https://dualhook.com/docs/whatsapp-error-131048) — quality/spam restriction * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-130429#faq) ----------------------------------------------------------- **Is 130429 a global app rate limit?** Not exactly — in Cloud API practice it's primarily the send-throughput limit for the business phone number. **What is the current default throughput?** Meta's Throughput doc says 80 messages per second by default per registered business phone number. **Will retrying immediately help?** No — it usually makes the burst worse. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-130429#how-dualhook-helps) ----------------------------------------------------------------------------------------- This is a **send-side** problem; throughput control belongs in your sender backend. Dualhook streams each outbound request to Meta without retaining the message body and does not automatically retry sends. It surfaces the current limit guidance in [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) and account visibility in [Messaging Health Status](https://dualhook.com/docs/health-monitoring) so you can understand the ceiling and stay under it with your queueing layer. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 131064: Template Classification Limit | Dualhook Browse Docs WhatsApp Error 131064: Messaging Limit from Template Classification Violations ============================================================================== WhatsApp error 131064 means your account hit a messaging limit due to template classification violations, affecting templates and direct sends. How to recover. > **TL;DR:** WhatsApp error 131064 means your account has reached a messaging limit because of repeated **template classification violations** (templates categorized incorrectly, e.g. marketing content submitted as utility). Meta's `details` string is **"Message failed to send because this account has reached its messaging limit due to template classification violations. This applies to both template messages and direct send messages."** The restriction lifts automatically after the enforcement period; correct your classifications. What Error 131064 Means[](https://dualhook.com/docs/whatsapp-error-131064#what-error-131064-means) --------------------------------------------------------------------------------------------------- 131064 is a throttling/enforcement error. When a business repeatedly misclassifies template categories (Marketing / Utility / Authentication), Meta applies a progressive messaging limit. Meta's official `details` value is: **"Message failed to send because this account has reached its messaging limit due to template classification violations. This applies to both template messages and direct send messages."** As 360dialog's reference summarizes it, the business has exceeded the threshold for template-category misclassification strikes and is being rate-limited as part of Meta's progressive enforcement. Crucially, it blocks **both** template messages and direct send messages — not just the offending templates. Meta states the restriction is **automatically lifted after the enforcement period** (Meta does not publish a fixed duration; do not assume a flat 24-hour reset). Where You See It[](https://dualhook.com/docs/whatsapp-error-131064#where-you-see-it) ------------------------------------------------------------------------------------- **Synchronous** `/messages` response when you attempt to send while the limit is in effect. Because it's account-scoped, all sends are affected, not just one template. Common Causes[](https://dualhook.com/docs/whatsapp-error-131064#common-causes) ------------------------------------------------------------------------------- * Submitting **marketing** content under a **utility** or **authentication** category to dodge pricing/limits. * Repeated category-correction strikes after Meta re-categorized your templates. * A pattern of misclassification across multiple templates pushing the account past the enforcement threshold. How to Fix It[](https://dualhook.com/docs/whatsapp-error-131064#how-to-fix-it) ------------------------------------------------------------------------------- 1. Review every template's **category** in WhatsApp Manager and confirm content matches the category (promotional → Marketing; transactional/account updates → Utility; OTP → Authentication). 2. **Re-create or re-categorize** misclassified templates correctly. 3. Wait out the **enforcement period** — Meta lifts the restriction automatically; you don't appeal a fixed window. 4. See Meta's **Template quality** and **Template categorization** docs for category rules. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-131064#how-to-prevent-it) --------------------------------------------------------------------------------------- * Categorize honestly at creation; don't label marketing as utility. * Monitor `template_category_update` webhooks so you know when Meta re-categorizes a template. * Periodically audit template content against current categorization guidelines. Related Errors[](https://dualhook.com/docs/whatsapp-error-131064#related-errors) --------------------------------------------------------------------------------- * [WhatsApp Error 131048](https://dualhook.com/docs/whatsapp-error-131048) — spam rate limit (quality-based). * [WhatsApp Error 131049](https://dualhook.com/docs/whatsapp-error-131049) — per-user marketing frequency cap. * [WhatsApp Error 368](https://dualhook.com/docs/whatsapp-error-368) — account restricted for policy violations. * [WhatsApp Webhook Override (Error 100)](https://dualhook.com/docs/whatsapp-error-100-webhook-override) * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-131064#faq) ----------------------------------------------------------- **Q: Does 131064 only block the bad templates?** A: No — it blocks **both** template and direct send messages account-wide for the enforcement period. **Q: How long does 131064 last?** A: Meta says the restriction is automatically lifted after the enforcement period and does not publish a fixed duration. Don't assume a flat 24-hour reset — fix classifications and monitor. **Q: Can I appeal 131064?** A: It's an automatic enforcement window that lifts on its own; fix classifications and wait it out. **Q: What's a "template classification violation"?** A: Submitting a template under the wrong category (commonly marketing content as utility/authentication); repeated strikes trigger progressive enforcement. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-131064#how-dualhook-helps) ----------------------------------------------------------------------------------------- The trigger and fix live in your payload logic and Meta's tools. Dualhook relays the send without persisting its content and can surface template state and category-update signals, helping you spot misclassifications before they accumulate into a 131064 limit. The re-categorization itself is done in WhatsApp Manager. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 131050: Marketing Opt-Out | Dualhook Browse Docs WhatsApp Error 131050: Recipient Opted Out of Marketing ======================================================= WhatsApp error 131050 means the recipient opted out of your marketing messages. You cannot override it — stop retrying and track opt-outs via user\_preferences. > **TL;DR:** WhatsApp error `131050` means the recipient chose to stop receiving marketing messages from your business on WhatsApp. You cannot override it — only the user can opt back in. Stop sending marketing templates to them, suppress them, and subscribe to the `user_preferences` webhook to track opt-outs. What Error 131050 Means[](https://dualhook.com/docs/whatsapp-error-131050#what-error-131050-means) --------------------------------------------------------------------------------------------------- Meta's `details` string is: **"Unable to deliver the message. This recipient has chosen to stop receiving marketing messages on WhatsApp from your business."** It's a per-business marketing opt-out. Meta's guidance: do not retry, and subscribe to the `user_preferences` webhook to be notified when users stop or resume marketing. Where You See It[](https://dualhook.com/docs/whatsapp-error-131050#where-you-see-it) ------------------------------------------------------------------------------------- Typically a **failed status webhook** (`status: failed`, `code: 131050`) in `statuses[].errors[]`. This is a preference-state failure, not a "retry later" deliverability blip. Common Causes[](https://dualhook.com/docs/whatsapp-error-131050#common-causes) ------------------------------------------------------------------------------- * The user explicitly opted out of marketing from your business. * Your suppression list didn't update everywhere, or one tool sent after another recorded the opt-out. * You treated a consent event like a temporary delivery failure. How to Fix It[](https://dualhook.com/docs/whatsapp-error-131050#how-to-fix-it) ------------------------------------------------------------------------------- 1. **Stop resending the same marketing message.** 2. Record the recipient as **marketing-suppressed** across all tools, numbers, and workflows. 3. Subscribe to the `user_preferences` webhook to capture opt-out/opt-in events. 4. Only resume marketing if the user later gives a fresh, compliant opt-in. You may still send permitted utility/service messages where appropriate. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-131050#how-to-prevent-it) --------------------------------------------------------------------------------------- * Maintain a single suppression source of truth and sync it across every campaign tool. * Separate marketing consent from generic service consent. * Keep marketing relevant and well-paced to reduce opt-out rate; enforce preferences before send-time. Related Errors[](https://dualhook.com/docs/whatsapp-error-131050#related-errors) --------------------------------------------------------------------------------- * [`/docs/whatsapp-error-131049`](https://dualhook.com/docs/whatsapp-error-131049) — marketing cap * [`/docs/whatsapp-error-131048`](https://dualhook.com/docs/whatsapp-error-131048) — spam-rate restriction * [`/docs/whatsapp-error-131026`](https://dualhook.com/docs/whatsapp-error-131026) — undeliverable * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-131050#faq) ----------------------------------------------------------- **What is WhatsApp error 131050?** The user opted out of your marketing messages. **Can I override 131050?** No — only the user can opt back in on their device. **How do I know who opted out?** Subscribe to the `user_preferences` webhook. **Does 131050 mean the user blocked the business?** Not necessarily — it specifically means they stopped your marketing messages. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-131050#how-dualhook-helps) ----------------------------------------------------------------------------------------- Opt-out tracking is a genuine Dualhook strength. Dualhook can route the `user_preferences` webhook to your endpoint so your suppression lists stay accurate — without ever reading message content. See [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) and [Maintain Account Health](https://dualhook.com/docs/account-health) . The decision to stop marketing to a contact is enforced in your sending backend. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 80007: WABA Rate Limit Reached | Dualhook Browse Docs WhatsApp Error 80007: WABA Rate Limit Reached ============================================= WhatsApp error 80007 means the WhatsApp Business Account hit its rate limit on management/Graph calls. How to back off, cache, and stop over-polling. > **TL;DR:** WhatsApp error `80007` means the WhatsApp Business Account has reached its rate limit — too many calls against the WABA in the current window. This is a WABA-level rate-limit problem, not message-send throughput. Back off, cache reads, and reduce polling. What Error 80007 Means[](https://dualhook.com/docs/whatsapp-error-80007#what-error-80007-means) ------------------------------------------------------------------------------------------------ Meta's `details` string is: **"The WhatsApp Business Account has reached its rate limit."** (Throttling error — confirmed verbatim on the live table.) Meta points to the WhatsApp Business account Rate Limits doc and advises trying again later or reducing the frequency/amount of API queries. **Note:** `80008` is not a code in Meta's current WhatsApp Cloud API error table — if you saw "80008" referenced elsewhere for "too many WABA calls," `80007` is the documented WhatsApp code; `4` is the app-level API call rate limit. Where You See It[](https://dualhook.com/docs/whatsapp-error-80007#where-you-see-it) ------------------------------------------------------------------------------------ An **immediate API error** on calls against the WABA (lookups, template management, assigned users, `subscribed_apps`, related admin/config APIs). It is not message-send throughput (`130429`). Common Causes[](https://dualhook.com/docs/whatsapp-error-80007#common-causes) ------------------------------------------------------------------------------ * Polling WABA/template endpoints too frequently. * Several background jobs querying the same WABA. * Repeated setup/debug calls during onboarding; poor caching of management reads. How to Fix It[](https://dualhook.com/docs/whatsapp-error-80007#how-to-fix-it) ------------------------------------------------------------------------------ 1. Back off immediately and retry later. 2. Cache management reads aggressively; batch admin reads where possible. 3. De-duplicate concurrent jobs polling the same WABA. 4. Replace polling with webhook-driven state where available. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-80007#how-to-prevent-it) -------------------------------------------------------------------------------------- * Design WABA admin tooling around caching and change events, not tight polling loops. * Stagger setup, audit, and monitoring jobs; don't confuse management polling with message-send traffic. * Per Meta's Graph API rate-limiting doc, the WhatsApp Business Management API allows **200 requests/hour per app, per WABA by default**, rising to **5,000 requests/hour per app, per active WABA once at least one phone number is registered**. Stay well under these with caching and webhooks. Related Errors[](https://dualhook.com/docs/whatsapp-error-80007#related-errors) -------------------------------------------------------------------------------- * [`/docs/whatsapp-error-130429`](https://dualhook.com/docs/whatsapp-error-130429) — send throughput limit * [`/docs/whatsapp-error-100-webhook-override`](https://dualhook.com/docs/whatsapp-error-100-webhook-override) — subscribe before override * [`/docs/whatsapp-error-190`](https://dualhook.com/docs/whatsapp-error-190) — invalid token on management calls * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-80007#faq) ---------------------------------------------------------- **Does 80007 block normal message sends?** It blocks the WABA/management calls hitting the limit; it's distinct from message-send throughput `130429`. **Is there an "error 80008"?** Not in Meta's current WhatsApp Cloud API error table. For WABA rate limits use `80007`; for app-level API call limits use `4`. **What's the biggest prevention win?** Stop over-polling — cache reads and use webhook/state-change patterns. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-80007#how-dualhook-helps) ---------------------------------------------------------------------------------------- This is a **platform/management** area where Dualhook helps by reducing the need for noisy manual polling. It surfaces connection and management status in a purpose-built way and routes account/webhook events so you don't poll WABA endpoints as hard. See [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) , [Messaging Health Status](https://dualhook.com/docs/health-monitoring) , and [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) . Inbound message content still routes directly from Meta to your endpoint. Outbound requests transit Dualhook's no-retention runtime without body logging or persistence. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Ice Breakers & Commands | Dualhook Browse Docs Ice Breakers & Commands ======================= Ice breakers and slash commands for WhatsApp business numbers, plus the removal of request\_welcome. (Note: for interactive buttons/lists/carousels, see Interactive Service Messages.) Overview[](https://dualhook.com/docs/welcome-ice-breakers-commands#overview) ----------------------------------------------------------------------------- Ice breakers and slash commands (collectively `conversational_components` in Meta's API) are in-chat UX features configured per WhatsApp business [phone number](https://dualhook.com/docs/phone-numbers) . They help users start and navigate conversations faster. **Note:** these are _not_ the same as interactive messages (CTA URL buttons, lists, carousels) — for those, see [Interactive Service Messages](https://dualhook.com/docs/service-messages-interactive) . Supported types: * **Ice Breakers** — tap-to-send starter prompts * **Commands** — slash commands with hints In Dualhook setups, webhook events for these interactions are delivered directly from Meta to your endpoint through [Webhook Override](https://dualhook.com/docs/webhook-override) . Meta removed the Welcome Messages feature and its `request_welcome` webhook type from Conversational Automation in March 2026. Opening a chat without sending anything no longer produces a webhook, so an integration cannot reliably detect that event. Send a welcome response after the customer's first actual inbound message instead. Ice Breakers[](https://dualhook.com/docs/welcome-ice-breakers-commands#ice-breakers) ------------------------------------------------------------------------------------- Tappable starter prompts shown when a user opens chat for the first time. * Up to 4 Ice Breakers per phone number * Up to 80 characters each * Emojis are not supported * Tapping sends a normal text message — the selected text appears in `messages[].text.body` If a user enters chat through a `wa.me` link with pre-filled text, the Ice Breaker UI can be dismissed. Commands[](https://dualhook.com/docs/welcome-ice-breakers-commands#commands) ----------------------------------------------------------------------------- Slash-prefixed text options users discover by typing `/` in the chat: * Up to 30 commands * Keyword max 32 characters * Hint max 256 characters * Emojis are not supported Command usage is delivered as a normal text message. Your backend should parse `messages[].text.body` and run the associated action. Implementation Notes[](https://dualhook.com/docs/welcome-ice-breakers-commands#implementation-notes) ----------------------------------------------------------------------------------------------------- * Treat Ice Breakers and Commands as plain inbound text events. * Keep webhook handling idempotent using message IDs. * Return `HTTP 200` quickly and process asynchronously. For setup instructions, see [Configure Ice Breakers & Commands](https://dualhook.com/docs/welcome-ice-breakers-commands-config) . For Dualhook customers, these components are configured in WhatsApp Manager at phone-number level. Related ------- * [Configure Ice Breakers & Commands](https://dualhook.com/docs/welcome-ice-breakers-commands-config) Step-by-step setup and testing of ice breakers and commands. * [Interactive Service Messages](https://dualhook.com/docs/service-messages-interactive) Interactive WhatsApp messages: CTA URL button, reply buttons, list, media carousel, and location request. Includes payload shapes and inbound-webhook formats for each type. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 132001: Template Not Found Fix | Dualhook Browse Docs WhatsApp Error 132001: Template Not Found ========================================= WhatsApp error 132001 means the template doesn't exist in that language/locale or isn't approved. Match the exact name, locale, approval state, and WABA context. > **TL;DR:** WhatsApp error `132001` means Meta could not resolve the template name/locale you requested, or the template isn't approved/usable in the WABA context you're sending from. Match the exact name (case-sensitive), the exact approved locale, the approval state, and the sending context. Blind retries do nothing. What Error 132001 Means[](https://dualhook.com/docs/whatsapp-error-132001#what-error-132001-means) --------------------------------------------------------------------------------------------------- Meta's `details` string is: **"The template does not exist in the specified language or the template has not been approved."** A common component-specific form is `template name () does not exist in `. The send request must reference an existing, approved template by its exact name and language locale. Where You See It[](https://dualhook.com/docs/whatsapp-error-132001#where-you-see-it) ------------------------------------------------------------------------------------- Usually an **immediate API error** (HTTP 400) from `POST //messages` (or `/marketing_messages`). It is **not** typically a `statuses[].errors[]` webhook, because the message is rejected before Meta accepts it. Common Causes[](https://dualhook.com/docs/whatsapp-error-132001#common-causes) ------------------------------------------------------------------------------- * Template name doesn't match WhatsApp Manager exactly (including case/underscores). * Wrong language locale (e.g. `en` vs `en_US`). * Template not yet approved, paused, rejected, deleted, or still propagating after approval/edit. * Switching from a test token to a live phone/WABA context where the template doesn't exist. How to Fix It[](https://dualhook.com/docs/whatsapp-error-132001#how-to-fix-it) ------------------------------------------------------------------------------- 1. Copy the exact template name from WhatsApp Manager (treat case/spelling as exact-match). 2. Match the approved language locale exactly. 3. Confirm the template status is **Approved** (not Pending/Rejected/Paused); if just created/edited, wait for propagation. 4. Verify sending context: correct `PHONE_NUMBER_ID`, WABA, token permissions, app/business asset. 5. If it works in test but fails in production, compare production phone/WABA against where the template is actually approved. Log `fbtrace_id` and escalate if all matches and it still fails. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-132001#how-to-prevent-it) --------------------------------------------------------------------------------------- * Keep a single source of truth for template metadata; never hand-type names in production. * Validate name + locale before queuing sends; add release checks against the approved catalog. * Avoid sending newly-approved templates at scale until propagation is visibly complete. Related Errors[](https://dualhook.com/docs/whatsapp-error-132001#related-errors) --------------------------------------------------------------------------------- * [`/docs/whatsapp-error-132000`](https://dualhook.com/docs/whatsapp-error-132000) — template variable mismatch * [`/docs/whatsapp-error-190`](https://dualhook.com/docs/whatsapp-error-190) — token/auth context problems * [`/docs/whatsapp-error-131000`](https://dualhook.com/docs/whatsapp-error-131000) — generic send failure * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-132001#faq) ----------------------------------------------------------- **Why do I get 132001 even though the template is approved?** Approval alone isn't enough — the request must match the exact name, locale, and WABA/phone context. **Why did it work with the temporary token but fail with the system-user token?** The live token is usually scoped to a different asset, or you changed phone/WABA between test and production. **Does 132001 mean the template is still syncing?** Sometimes, especially right after approval/recreation — but check name, locale, and context first. **Can 132001 happen on the Marketing Messages API too?** Yes, the same resolution problem applies if the named template/locale doesn't resolve. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-132001#how-dualhook-helps) ----------------------------------------------------------------------------------------- Templates are an area where Dualhook genuinely helps. With **advanced `whatsapp_business_management` access**, Dualhook can surface your templates' names, locales, and approval/quality status so you catch a 132001 cause before sending, and confirm the connected WABA/phone setup. See [Display Names](https://dualhook.com/docs/display-names) and [Messaging Health Status](https://dualhook.com/docs/health-monitoring) . Your backend still constructs the standard Cloud API payload, then sends it through Dualhook's allowlisted runtime without content persistence or body logging. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp account_alerts Webhook: Alerts, Payloads, Dualhook Handling | Dualhook Browse Docs account\_alerts Webhook ======================= Field reference for WhatsApp account\_alerts webhooks: messaging-limit alerts, OBA status, profile-photo alerts, payload shape, and Dualhook handling. The `account_alerts` field reports account and phone-number alerts that Meta wants the business or partner to act on. It is a management webhook field, not a message-path field. What account\_alerts Reports[](https://dualhook.com/docs/webhook-account-alerts#what-account_alerts-reports) ------------------------------------------------------------------------------------------------------------- Use `account_alerts` to detect changes such as: * messaging-limit increase decisions that were denied, deferred, or need more information, * Official Business Account (OBA) approval or rejection, * deleted business profile photos, * profile or capability alerts that may require a business action. The important nested object is `alert_info`. It carries the severity, status, type, and human-readable description. When It Fires[](https://dualhook.com/docs/webhook-account-alerts#when-it-fires) -------------------------------------------------------------------------------- Common trigger families are: | Trigger | Typical meaning | | --- | --- | | Increased-capability eligibility | Meta cannot increase limits yet, deferred the decision, or needs more verification. | | OBA status | The phone number's Official Business Account request was approved or rejected. | | Profile photo lost | The phone number's public business profile photo was deleted and should be uploaded again. | Payload Shape[](https://dualhook.com/docs/webhook-account-alerts#payload-shape) -------------------------------------------------------------------------------- { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "time": 1739321024,\ "changes": [\ {\ "field": "account_alerts",\ "value": {\ "entity_type": "BUSINESS",\ "entity_id": "",\ "alert_info": {\ "alert_severity": "WARNING",\ "alert_status": "ACTIVE",\ "alert_type": "INCREASED_CAPABILITIES_ELIGIBILITY_DEFERRED",\ "alert_description": "Limits cannot be increased yet."\ }\ }\ }\ ]\ }\ ] } Key values: | Field | Notes | | --- | --- | | `entity_type` | Usually `BUSINESS`, `PHONE_NUMBER`, or a status/profile entity. | | `entity_id` | Business portfolio ID, phone number ID, or profile/status entity ID. | | `alert_severity` | `CRITICAL`, `WARNING`, or `INFORMATIONAL`. | | `alert_status` | Usually `ACTIVE` or `NONE`. | | `alert_type` | Machine-readable reason, such as `OBA_APPROVED`, `OBA_REJECTED`, or an increased-capability eligibility result. | | `alert_description` | Human-readable action or explanation from Meta. | Dualhook Handling[](https://dualhook.com/docs/webhook-account-alerts#dualhook-handling) ---------------------------------------------------------------------------------------- Dualhook subscribes to `account_alerts` as a management field. These events land on Dualhook's app-level callback, not on your Webhook Override URL. Dualhook uses the event metadata to surface account-health and capability alerts in the dashboard, classify alert severity, and forward the management event to your configured endpoint with Dualhook forwarding headers. Dualhook stores operational metadata about the alert and delivery status; it does not store message content because this field is not a message webhook. Implementation Notes[](https://dualhook.com/docs/webhook-account-alerts#implementation-notes) ---------------------------------------------------------------------------------------------- * Treat `alert_type` as the stable routing key and `alert_description` as display text. * Escalate `CRITICAL` alerts to an operator; they often indicate a rejection or blocked capability. * Keep the raw `entity_type` and `entity_id`, because an alert may target a business portfolio, phone number, or profile entity. * For OBA decisions, also check [Official Business Account](https://dualhook.com/docs/official-business-account) and [Display Names](https://dualhook.com/docs/display-names) . Related[](https://dualhook.com/docs/webhook-account-alerts#related) -------------------------------------------------------------------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) * [Maintain Account Health](https://dualhook.com/docs/account-health) * [Official Business Account](https://dualhook.com/docs/official-business-account) Related ------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting. * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) Management webhook events: template status, quality updates, account alerts, and forwarding. * [Maintain Account Health](https://dualhook.com/docs/account-health) Ownership hygiene, quality monitoring, opt-out governance, and incident playbook. * [Official Business Account (OBA)](https://dualhook.com/docs/official-business-account) The WhatsApp blue checkmark: eligibility, notability requirements, and limitations. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Templates | Dualhook Browse Docs Templates ========= Template lifecycle, categories, and management through Dualhook and Meta Graph API. Overview[](https://dualhook.com/docs/templates#overview) --------------------------------------------------------- Templates are pre-approved message formats used when you message customers outside the active 24-hour customer service window. They are created at the [WABA](https://dualhook.com/docs/whatsapp-business-accounts) level and sent from a specific `phone_number_id`. In Dualhook, templates can be synced, created, edited, duplicated, and deleted from the dashboard. Customers can also use the Graph-compatible `dh_live_` [Runtime API template routes](https://dualhook.com/docs/runtime-api-reference#message-templates) to upload media-header samples and list, create, inspect status/quality/rejection details, update, and delete templates on the connection's stored WABA. Never submit the connection's Meta token or `appsecret_proof`; Dualhook applies both internally and verifies template ownership before object-ID reads or mutations. Categories[](https://dualhook.com/docs/templates#categories) ------------------------------------------------------------- Supported categories: * **MARKETING** — promotional content, offers, and announcements. See [Marketing Messages](https://dualhook.com/docs/marketing-messages) . Marketing templates are the only category that can be sent through either Cloud API `/messages` or [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) `/marketing_messages`. * **UTILITY** — order updates, shipping notifications, and account alerts. See [Utility Messages](https://dualhook.com/docs/utility-messages) . * **AUTHENTICATION** — one-time passwords and verification codes. See [Authentication Messages](https://dualhook.com/docs/authentication-messages) . Meta can reclassify template categories based on actual content. Dualhook surfaces category and status updates after sync. Template Lifecycle[](https://dualhook.com/docs/templates#template-lifecycle) ----------------------------------------------------------------------------- Common statuses: * `PENDING` — submitted, awaiting review * `APPROVED` — accepted and ready to send * `REJECTED` — not accepted; revise and resubmit * `PAUSED` — temporarily suspended due to quality * `DISABLED` — permanently disabled * `IN_APPEAL` — rejection is being challenged Templates that are pending review or disabled cannot be edited until their status changes. You can duplicate any template to create a new version for resubmission. Management API Examples[](https://dualhook.com/docs/templates#management-api-examples) --------------------------------------------------------------------------------------- ### Create Template[](https://dualhook.com/docs/templates#create-template) For an image, video, or document header, first upload a sample through `POST /v25.0//message_template_media`, then place the returned `h` value in `example.header_handle`. Dualhook keeps Meta's app ID and resumable upload session internal. See the [Runtime API example](https://dualhook.com/docs/runtime-api-reference#message-templates) . { "name": "order_update_v1", "language": "en_US", "category": "UTILITY", "components": [\ {\ "type": "BODY",\ "text": "Hi {{1}}, your order {{2}} is now {{3}}.",\ "example": {\ "body_text": [["Alex", "A12345", "shipped"]]\ }\ }\ ] } ### List Templates[](https://dualhook.com/docs/templates#list-templates) GET //message_templates?fields=id,name,language,status,category,rejected_reason,components ### Update Template Components[](https://dualhook.com/docs/templates#update-template-components) { "components": [\ {\ "type": "BODY",\ "text": "Hi {{1}}, your order {{2}} is now {{3}}. Track here: {{4}}",\ "example": {\ "body_text": [["Alex", "A12345", "shipped", "https://example.com/track/A12345"]]\ }\ }\ ] } ### Delete Template[](https://dualhook.com/docs/templates#delete-template) DELETE //message_templates?name=order_update_v1 For template structure details, see [Template Elements](https://dualhook.com/docs/template-elements) . For send payloads, see [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) . Related ------- * [Template Elements](https://dualhook.com/docs/template-elements) Template structure: headers, body, footer, buttons, variables, and practical limits. * [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) How to send template messages via Cloud API with variables, media headers, and URL buttons. * [Template Analytics](https://dualhook.com/docs/template-analytics) Measuring template performance: sent, delivered, read, and clicked metrics. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Sending Template Messages | Dualhook Browse Docs Sending Template Messages ========================= How to send template messages via Cloud API with variables, media headers, and URL buttons. Standard Send Endpoint[](https://dualhook.com/docs/sending-template-messages#standard-send-endpoint) ----------------------------------------------------------------------------------------------------- Send [template](https://dualhook.com/docs/templates) messages through: POST //messages Utility Template with Variables[](https://dualhook.com/docs/sending-template-messages#utility-template-with-variables) ----------------------------------------------------------------------------------------------------------------------- { "messaging_product": "whatsapp", "to": "12015550123", "type": "template", "template": { "name": "order_update_v1", "language": { "code": "en_US" }, "components": [\ {\ "type": "body",\ "parameters": [\ { "type": "text", "text": "Alex" },\ { "type": "text", "text": "A12345" },\ { "type": "text", "text": "shipped" }\ ]\ }\ ] } } Template with Media Header[](https://dualhook.com/docs/sending-template-messages#template-with-media-header) ------------------------------------------------------------------------------------------------------------- { "messaging_product": "whatsapp", "to": "12015550123", "type": "template", "template": { "name": "delivery_photo_update", "language": { "code": "en_US" }, "components": [\ {\ "type": "header",\ "parameters": [\ {\ "type": "image",\ "image": { "id": "123456789012345" }\ }\ ]\ },\ {\ "type": "body",\ "parameters": [\ { "type": "text", "text": "Alex" },\ { "type": "text", "text": "A12345" }\ ]\ }\ ] } } For media upload, see [Upload, Retrieve & Delete Media](https://dualhook.com/docs/media-upload) . URL Button Parameter[](https://dualhook.com/docs/sending-template-messages#url-button-parameter) ------------------------------------------------------------------------------------------------- { "messaging_product": "whatsapp", "to": "12015550123", "type": "template", "template": { "name": "track_order_button", "language": { "code": "en_US" }, "components": [\ {\ "type": "body",\ "parameters": [\ { "type": "text", "text": "Alex" },\ { "type": "text", "text": "A12345" }\ ]\ },\ {\ "type": "button",\ "sub_type": "url",\ "index": "0",\ "parameters": [\ { "type": "text", "text": "A12345" }\ ]\ }\ ] } } Send Responses[](https://dualhook.com/docs/sending-template-messages#send-responses) ------------------------------------------------------------------------------------- Successful send: { "messages": [\ {\ "id": "wamid.HBgLMTIzNDU2Nzg5FQIAERgS...",\ "message_status": "accepted"\ }\ ] } Held for quality assessment: { "messages": [\ {\ "id": "wamid.HBgLMTIzNDU2Nzg5FQIAERgS...",\ "message_status": "held_for_quality_assessment"\ }\ ] } Dualhook manages template operations and management-event visibility but does not change Meta review outcomes, category assignment, or send eligibility decisions. For error codes, see [Meta API Error Reference](https://dualhook.com/docs/api-errors) . Related ------- * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. * [Template Elements](https://dualhook.com/docs/template-elements) Template structure: headers, body, footer, buttons, variables, and practical limits. * [Messaging Overview](https://dualhook.com/docs/messaging-overview) Core messaging endpoint, text messages, formatting, delivery statuses, and common errors. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Marketing Deep Links | Dualhook Browse Docs Marketing Deep Links ==================== Map a URL button on a marketing template to an Android app deep link via app\_deep\_link, with fallback URLs for non-Android clients and uninstalled-app users. What Marketing Deep Links Do[](https://dualhook.com/docs/marketing-deep-links#what-marketing-deep-links-do) ------------------------------------------------------------------------------------------------------------ A marketing deep link is an Android `app_deep_link` configuration on a URL button inside a marketing template. When the recipient taps the button on an Android device, WhatsApp tries to open your app at the specified deep-link URI instead of opening the website. iOS and non-Android clients always fall back to the website URL. This is exclusive to [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) — Cloud API marketing templates do not support `app_deep_link`. If you create a template with this component and send it through Cloud API, the deep link is ignored and the user lands on the fallback website URL. Where They Apply[](https://dualhook.com/docs/marketing-deep-links#where-they-apply) ------------------------------------------------------------------------------------ * Only on **URL buttons** inside marketing templates. * Only triggered on Android devices that have the target app installed. * Conversion attribution to MM API marketing campaigns requires the receiving app to correctly preserve `fbclid` from the deep link — see [Conversion Tracking → Android Deep Link Attribution](https://dualhook.com/docs/marketing-conversion-tracking#android-deep-link-attribution) . Create via WhatsApp Manager[](https://dualhook.com/docs/marketing-deep-links#create-via-whatsapp-manager) ---------------------------------------------------------------------------------------------------------- 1. Open WhatsApp Manager and navigate to **Message templates → Manage templates**. 2. Click **Create template**. 3. Select **Marketing** tab → **Custom** radio → **Next**. 4. In the **Buttons** section, choose **\+ Add buttons → Visit website**. 5. Check **Track app conversions** to expose the deep-link fields. 6. Fill in the deep-link configuration (see field reference below). 7. Add other components, name the template, submit for approval. You can edit an existing template later to add the deep-link fields, but doing so triggers another template review cycle. Create via Message Templates API[](https://dualhook.com/docs/marketing-deep-links#create-via-message-templates-api) -------------------------------------------------------------------------------------------------------------------- curl 'https://graph.facebook.com///message_templates' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "summer_deals_deep_link_v1", "language": "en_US", "category": "marketing", "components": [\ { "type": "header", "format": "text", "text": "Summer Deals" },\ { "type": "body", "text": "Beat the heat with deals on succulents! Use the button below to see our Summer Steals!" },\ {\ "type": "buttons",\ "buttons": [\ {\ "type": "url",\ "text": "View Deals",\ "url": "https://www.example.com/deals/summer/",\ "app_deep_link": {\ "meta_app_id": 123456789012345,\ "android_deep_link": "myapp://deals/summer/",\ "android_fallback_playstore_url": "https://www.example.com/deals/summer/"\ }\ }\ ]\ }\ ] }' The `app_deep_link` object lives **inside** the URL button object, not at the root of the button array. Field Reference[](https://dualhook.com/docs/marketing-deep-links#field-reference) ---------------------------------------------------------------------------------- | Field | Required? | What it is | | --- | --- | --- | | `url` (button-level) | Yes | The fallback website URL. Used on iOS, on Android when the app is not installed, and on every non-Android client. Loaded in the device's default browser. | | `app_deep_link.meta_app_id` | Yes (for `app_deep_link`) | The numeric Meta app ID associated with your business portfolio that owns the access token used to send. | | `app_deep_link.android_deep_link` | Yes (for `app_deep_link`) | The Android deep-link URI. Custom scheme (`myapp://path`) or HTTPS App Link. | | `app_deep_link.android_fallback_playstore_url` | Yes (for `app_deep_link`) | URL loaded if WhatsApp cannot open the deep-link URI on Android — typically your Play Store listing or a web page that bounces the user toward installing the app. | `meta_app_id` is the same numeric ID you'd find on the Meta App Dashboard for the app whose access token is sending the template. If you use multiple Meta apps, the deep link binds to the specific app whose token sends the message. Fallback Behavior[](https://dualhook.com/docs/marketing-deep-links#fallback-behavior) -------------------------------------------------------------------------------------- The order of resolution when the user taps a URL button with `app_deep_link`: 1. **Android with app installed and intent filter matches** → WhatsApp opens your app at `android_deep_link`. 2. **Android with app missing or intent filter mismatch** → WhatsApp loads `android_fallback_playstore_url` in the default browser. 3. **iOS / desktop / web client** → WhatsApp loads the button's `url` (website fallback) in the default browser. If `android_fallback_playstore_url` is omitted, the WhatsApp client falls back to the button's main `url` instead — but Meta recommends always specifying a fallback to control where uninstalled-app users land. Attribution and fbclid[](https://dualhook.com/docs/marketing-deep-links#attribution-and-fbclid) ------------------------------------------------------------------------------------------------ Meta appends `fbclid` to whichever URL is actually opened — both the deep-link URI and the fallback URLs receive it. The receiving Android app must extract and persist it; otherwise conversion attribution silently fails. The full Android implementation requirements are in [Conversion Tracking → Android Deep Link Attribution](https://dualhook.com/docs/marketing-conversion-tracking#android-deep-link-attribution) . Common pitfalls for app implementers: * Reading the URI in `onCreate()` only and ignoring `onNewIntent()`. * Stripping query params when rebuilding the URI for an internal route. * Not persisting attribution params across sessions. * Failing to install Pixel/Conversions API on the website fallback URL — uninstalled-app users go there and never produce attributed events. Related ------- * [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) Reference for Meta's separate /marketing\_messages product, which is not exposed by Dualhook's runtime or Embedded Signup configuration. * [Marketing Conversion Tracking](https://dualhook.com/docs/marketing-conversion-tracking) Measure conversions from marketing messages with Meta Pixel, Conversions API, and the Meta SDK. Includes fbclid handling, the click-event webhook, and Android deep-link attribution. * [Marketing Messages](https://dualhook.com/docs/marketing-messages) Marketing template messaging through Dualhook's standard Cloud API runtime, plus boundaries for Meta's separate MM API product. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp smb_app_state_sync Webhook: Business App Contact Sync | Dualhook Browse Docs smb\_app\_state\_sync Webhook ============================= Field reference for WhatsApp smb\_app\_state\_sync webhooks: Coexistence contact sync, add/remove actions, direct routing, and Dualhook handling. Looking for the high-level overview first? Start with [WhatsApp Coexistence](https://dualhook.com/whatsapp-coexistence) and then return here for the implementation details. The `smb_app_state_sync` field synchronizes WhatsApp Business app contact state for a business customer onboarded through a solution provider. In Dualhook setups, it is direct-routed to your endpoint via Webhook Override. What smb\_app\_state\_sync Reports[](https://dualhook.com/docs/webhook-smb-app-state-sync#what-smb_app_state_sync-reports) --------------------------------------------------------------------------------------------------------------------------- This field reports contact-book changes from the WhatsApp Business app: * a contact was added, * a contact was edited, * a contact was removed, * the initial app contact sync was triggered after onboarding. The payload is not a Cloud API message send or delivery status. It is contact state from the business customer's WhatsApp Business app. When It Fires[](https://dualhook.com/docs/webhook-smb-app-state-sync#when-it-fires) ------------------------------------------------------------------------------------ Meta sends this field when a provider synchronizes the business app contacts after onboarding, and later when the business customer adds, edits, or removes contacts in the WhatsApp Business app address book. Payload Shape[](https://dualhook.com/docs/webhook-smb-app-state-sync#payload-shape) ------------------------------------------------------------------------------------ { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "changes": [\ {\ "field": "smb_app_state_sync",\ "value": {\ "messaging_product": "whatsapp",\ "metadata": {\ "display_phone_number": "15550783881",\ "phone_number_id": "106540352242922"\ },\ "state_sync": [\ {\ "type": "contact",\ "contact": {\ "full_name": "Pablo Morales",\ "first_name": "Pablo",\ "phone_number": "16505551234"\ },\ "action": "add",\ "metadata": {\ "timestamp": "1739321024"\ }\ }\ ]\ }\ }\ ]\ }\ ] } | Field | Notes | | --- | --- | | `state_sync[].type` | Currently contact sync uses `contact`. | | `state_sync[].action` | `add` means added or edited; `remove` means removed. | | `contact.full_name` / `contact.first_name` | Present for add/edit, omitted for removals. | | `contact.phone_number` | WhatsApp contact phone number. | | `metadata.timestamp` | Timestamp for the contact-state event. | Dualhook Handling[](https://dualhook.com/docs/webhook-smb-app-state-sync#dualhook-handling) -------------------------------------------------------------------------------------------- Dualhook does not ingest, store, forward, or replay `smb_app_state_sync` payloads. This field is direct-routed through Webhook Override, so Meta sends contact-sync payloads to your configured endpoint. During Coexistence onboarding, Dualhook triggers Meta's one-time contact sync before requesting the optional history sync. If Meta rejects the contact request, Dualhook still attempts history because Meta treats them as separate one-time requests. Keep WhatsApp Business open while Meta prepares the data. A successful trigger only means Meta accepted the sync request; it does not prove that contacts were delivered or that your endpoint processed them. Future app contact changes also go Meta → your endpoint. If your endpoint misses contact-sync payloads, Dualhook has no stored copy to replay. Implementation Notes[](https://dualhook.com/docs/webhook-smb-app-state-sync#implementation-notes) -------------------------------------------------------------------------------------------------- * Treat `add` as upsert. Meta uses it for both new and edited contacts. * Treat `remove` as deletion or tombstone, and do not require name fields on remove events. * Process asynchronously if the initial sync includes many contacts. * Deduplicate by WABA ID, phone number ID, contact phone number, action, and timestamp. * Review your privacy posture: once your endpoint receives contact sync, your system becomes the contact-data storage boundary. Related[](https://dualhook.com/docs/webhook-smb-app-state-sync#related) ------------------------------------------------------------------------ * [Webhook Override](https://dualhook.com/docs/webhook-override) * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) * [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) * [history webhook](https://dualhook.com/docs/webhook-history) * [Compliance & Data Retention](https://dualhook.com/docs/compliance) Related ------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) How Coexistence mode works: using WhatsApp Business App and Cloud API on the same number. * [history Webhook](https://dualhook.com/docs/webhook-history) Field reference for WhatsApp history webhooks: Coexistence chat-history sync, phases, chunks, declined sharing, direct routing, and Dualhook handling. * [Compliance & Data Retention](https://dualhook.com/docs/compliance) Privacy boundary, configurable retention, CSV export, and organization isolation. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Upload, Retrieve & Delete Media | Dualhook Browse Docs Upload, Retrieve & Delete Media =============================== Media lifecycle: upload files, retrieve download URLs, and delete media objects. Overview[](https://dualhook.com/docs/media-upload#overview) ------------------------------------------------------------ Media objects are managed through dedicated, connection-scoped runtime endpoints: * **Upload:** `POST //media` * **Retrieve metadata/URL:** `GET /` * **Delete:** `DELETE /` Media lifecycle: 1. Upload file and receive `media_id`. 2. Reuse `media_id` in [message payloads](https://dualhook.com/docs/media-messages) . 3. Retrieve temporary download URL when needed. 4. Delete media when no longer needed. Upload Media[](https://dualhook.com/docs/media-upload#upload-media) -------------------------------------------------------------------- Upload uses `multipart/form-data`: curl -X POST "https://api.dualhook.com/v25.0//media" \ -H "Authorization: Bearer " \ -F "messaging_product=whatsapp" \ -F "file=@;type=" Response: { "id": "" } Retrieve Media Metadata[](https://dualhook.com/docs/media-upload#retrieve-media-metadata) ------------------------------------------------------------------------------------------ curl -X GET "https://api.dualhook.com/v25.0/" \ -H "Authorization: Bearer " Response: { "messaging_product": "whatsapp", "url": "https://api.dualhook.com/v25.0//content", "mime_type": "", "sha256": "", "file_size": 12345, "id": "" } The returned URL is a stable, authenticated Dualhook content route. It obtains Meta's short-lived URL only when requested and never exposes that Meta URL to your application. Download Media Binary[](https://dualhook.com/docs/media-upload#download-media-binary) -------------------------------------------------------------------------------------- Download through the authenticated content route: curl -X GET "https://api.dualhook.com/v25.0//content" \ -H "Authorization: Bearer " \ --output "" Delete Media[](https://dualhook.com/docs/media-upload#delete-media) -------------------------------------------------------------------- curl -X DELETE "https://api.dualhook.com/v25.0/" \ -H "Authorization: Bearer " Response: { "success": true } Security and Compliance[](https://dualhook.com/docs/media-upload#security-and-compliance) ------------------------------------------------------------------------------------------ * Treat media content routes and Dualhook API keys as sensitive data. * Do not log bearer keys or media bodies. * Restrict media retention according to your [compliance policy](https://dualhook.com/docs/compliance) . * Verify file type and scan uploaded files before downstream processing. * Use queue-based media processing for spikes. * Retry transient upload/download failures with backoff. Do not automatically retry message sends. * Add idempotent job keys so repeated webhook events do not duplicate downloads. * Dualhook streams media without persistence or caching and excludes media content and Meta download URLs from operational logs. Related ------- * [Media Messages](https://dualhook.com/docs/media-messages) Sending and receiving images, video, audio, documents, and stickers via Cloud API. * [Messaging Overview](https://dualhook.com/docs/messaging-overview) Core messaging endpoint, text messages, formatting, delivery statuses, and common errors. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 2655093: Already Shared With a Partner | Dualhook Browse Docs WhatsApp Error 2655093: Account Already Shared With a Partner ============================================================= How to safely release an old BSP or Coexistence partner relationship when Meta blocks Embedded Signup with error 2655093. Meta error `#2655093` says that the WhatsApp Business account is already shared with a partner and that the current Embedded Signup flow cannot switch partners. What the Error Means[](https://dualhook.com/docs/whatsapp-error-2655093#what-the-error-means) ---------------------------------------------------------------------------------------------- The phone's WhatsApp Business **Business Platform** relationship still points to a previous BSP or Tech Provider. That relationship is not the same as a Business Manager role, system user, connected app, or visible entry on the WABA's Partners tab. The block occurs before Embedded Signup grants Dualhook access. Dualhook therefore cannot inspect the WABA, identify the hidden partner, remove another provider's relationship, or open an asset-specific Meta case as the new provider. Safe Resolution Steps[](https://dualhook.com/docs/whatsapp-error-2655093#safe-resolution-steps) ------------------------------------------------------------------------------------------------ 1. **Do not delete the number, WABA, or WhatsApp Business account.** 2. Update the WhatsApp Business app to the latest version. 3. On the phone using the number, open **WhatsApp Business → Settings → Account → Business Platform**. 4. Select the currently connected platform and tap **Disconnect**. 5. Wait at least 15 minutes before retrying Embedded Signup. 6. If the previous BSP has its own disconnect or release control, complete that process too. 7. Ask the previous BSP to confirm that it fully offboarded the **WhatsApp Business App Coexistence partner relationship** for both the WABA and phone number—not merely that the number disappeared from its dashboard or API. 8. Retry from Dualhook using the clean-browser steps in [Connection Not Appearing](https://dualhook.com/docs/connection-not-appearing) . If No Partner Is Visible[](https://dualhook.com/docs/whatsapp-error-2655093#if-no-partner-is-visible) ------------------------------------------------------------------------------------------------------ If WhatsApp Business already shows **Connect to the business platform** and Meta still returns `#2655093`, the previous partner relationship may be stale on Meta's backend. Ask the previous provider to escalate through its Meta partner support channel. If your business can access [Meta Business Support](https://business.facebook.com/business-support-home/) , you can also open a case there with the previous provider's written offboarding confirmation. Removing Business Manager roles, apps, partner permissions, or system-user tokens may be useful account hygiene, but it does not prove that the phone-level Coexistence relationship was removed. Related Error Codes[](https://dualhook.com/docs/whatsapp-error-2655093#related-error-codes) -------------------------------------------------------------------------------------------- | Code | Meaning and safe next step | | --- | --- | | `2655093` | Another partner still owns the Coexistence-sharing relationship. Disconnect it in WhatsApp Business, then obtain full offboarding from the previous provider. | | `2655094` | Meta still sees a Business Platform connection. Disconnect it in the app and wait at least 15 minutes. | | `3441049` | The phone is already registered with a Business Platform product. Disconnect that platform in the app and wait at least 15 minutes. | | `2655049` | The WABA is already shared with another partner. Unshare it or ask that partner to release it. | | `2494028` | The account is shared with another partner. Unshare the existing relationship before retrying. | | `2494119` | Meta detected a duplicate service provider under the parent WABA. The existing provider relationship must be removed. | See [Embedded Signup Errors](https://dualhook.com/docs/embedded-signup-errors) for other signup codes. What Not to Do[](https://dualhook.com/docs/whatsapp-error-2655093#what-not-to-do) ---------------------------------------------------------------------------------- Do not treat any of these as a standard fix for `#2655093` unless Meta explicitly instructs you for your case: * deleting the WABA or phone number * deregistering and re-registering the number * uninstalling WhatsApp Business * creating another WABA for the same number * revoking unrelated system users or permissions * adding a payment method when the only displayed problem is `#2655093` These actions address different problems and may affect linked devices, service continuity, or message history. Escalation Checklist[](https://dualhook.com/docs/whatsapp-error-2655093#escalation-checklist) ---------------------------------------------------------------------------------------------- Send the previous BSP or Meta: Embedded Signup error: #2655093 Meta session/error ID: Phone number (with country code): Meta Business Portfolio name and ID: WABA name and ID: Previous BSP/Tech Provider: WhatsApp Business currently shows: Business Manager Partners tab currently shows: Please confirm that the WhatsApp Business App Coexistence partner-sharing relationship has been fully offboarded for both the WABA and phone number, not only removed from your dashboard/API. If offboarding is complete, please escalate the stale Meta partner relationship through your partner channel. Attach screenshots of the error, the WhatsApp Business Business Platform screen, the WABA Partners screen, and the previous provider's written confirmation. Related ------- * [Embedded Signup Errors](https://dualhook.com/docs/embedded-signup-errors) Searchable Meta Embedded Signup error index with safe first actions for business, WABA, phone, OTP, sharing, and account restrictions. * [Connection Not Appearing After Embedded Signup](https://dualhook.com/docs/connection-not-appearing) Why Meta can finish linking while no Dualhook connection appears, and how to retry the browser handoff safely. * [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) How Coexistence mode works: using WhatsApp Business App and Cloud API on the same number. * [How to Leave Your WhatsApp BSP and Own Your WABA](https://dualhook.com/docs/leave-whatsapp-bsp-own-your-waba) Migrate a WhatsApp business phone number away from a Solution Partner: what Meta preserves, what resets, the prerequisites, billing during migration, and the Coexistence caveat. * [Support, Contact, and Bug Reports](https://dualhook.com/docs/contact-support) How to contact Dualhook support, use the in-app support chat, report bugs, attach screenshots or PDFs, and know when email is the right path. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp message_template_status_update Webhook Reference | Dualhook Browse Docs message\_template\_status\_update Webhook ========================================= Field reference for WhatsApp message\_template\_status\_update webhooks: template approval, rejection, pause, disablement, archive, deletion, appeal, and Dualhook handling. The `message_template_status_update` field reports lifecycle changes for an existing WhatsApp template. It is the primary webhook field for approval, rejection, pause, disablement, archive, deletion, and appeal status. What message\_template\_status\_update Reports[](https://dualhook.com/docs/webhook-message-template-status-update#what-message_template_status_update-reports) --------------------------------------------------------------------------------------------------------------------------------------------------------------- This field identifies the template and reports the current lifecycle event, category, language, and any reason or extra metadata available for that event. It is especially important for: * moving newly created templates from pending to approved or rejected, * detecting templates that were paused, disabled, or flagged, * showing rejection reasons and recommendations, * keeping archived or deleted template state in sync. When It Fires[](https://dualhook.com/docs/webhook-message-template-status-update#when-it-fires) ------------------------------------------------------------------------------------------------ Meta sends it when a template is approved, rejected, disabled, archived, unarchived, paused, flagged, locked, reinstated, deleted, or enters appeal. Event Values[](https://dualhook.com/docs/webhook-message-template-status-update#event-values) ---------------------------------------------------------------------------------------------- Common `event` values include: | Event | Meaning | | --- | --- | | `APPROVED` | Template can be sent. | | `REJECTED` | Template failed review. Check `reason` and possibly `rejection_info`. | | `PENDING` | Template is still in review. | | `FLAGGED` | Negative feedback puts the template at risk. | | `PAUSED` | Template is temporarily paused. | | `DISABLED` | Template has been disabled due to feedback or policy. | | `LOCKED` | Template cannot be edited. | | `IN_APPEAL` | Template is in the appeal process. | | `REINSTATED` | Template is usable again. | | `ARCHIVED` / `UNARCHIVED` | Template moved into or out of archived state. | | `DELETED` / `PENDING_DELETION` | Template was deleted or scheduled for deletion. | | `LIMIT_EXCEEDED` | The WABA reached its template limit. | Payload Shape[](https://dualhook.com/docs/webhook-message-template-status-update#payload-shape) ------------------------------------------------------------------------------------------------ { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "time": 1751247548,\ "changes": [\ {\ "field": "message_template_status_update",\ "value": {\ "event": "REJECTED",\ "message_template_id": 1689556908129835,\ "message_template_name": "abandoned_cart",\ "message_template_language": "en",\ "reason": "INVALID_FORMAT",\ "message_template_category": "MARKETING",\ "rejection_info": {\ "reason": "Variables are not clearly separated.",\ "recommendation": "Add descriptive text between variables."\ }\ }\ }\ ]\ }\ ] } Optional nested objects: | Object | Used for | | --- | --- | | `disable_info` | Disable date when the template is disabled. | | `other_info` | Pause, unpause, lock, or unlock details. | | `rejection_info` | Detailed invalid-format rejection reason and recommendation. | Dualhook Handling[](https://dualhook.com/docs/webhook-message-template-status-update#dualhook-handling) -------------------------------------------------------------------------------------------------------- Dualhook subscribes to `message_template_status_update` as a management field. It uses the event to update template status in the dashboard, show review outcomes, classify template problems, and forward the management event to your configured endpoint. Only template metadata and operational delivery status are stored. Outbound message bodies may transit the allowlisted runtime but are not persisted, cached, or body-logged. Implementation Notes[](https://dualhook.com/docs/webhook-message-template-status-update#implementation-notes) -------------------------------------------------------------------------------------------------------------- * Treat `message_template_id` plus language as the safest identity pair. * For `REJECTED`, show both `reason` and `rejection_info` when present. * For `PAUSED`, `FLAGGED`, and `DISABLED`, slow or stop sends that use the affected template. * Keep unknown `event` values. Meta adds lifecycle events over time. * Do not assume `reason` is always a string; scheduled deletion events can report `null`. Related[](https://dualhook.com/docs/webhook-message-template-status-update#related) ------------------------------------------------------------------------------------ * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) * [Templates](https://dualhook.com/docs/templates) * [Template Elements](https://dualhook.com/docs/template-elements) * [message\_template\_quality\_update webhook](https://dualhook.com/docs/webhook-message-template-quality-update) Related ------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting. * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) Management webhook events: template status, quality updates, account alerts, and forwarding. * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. * [Template Elements](https://dualhook.com/docs/template-elements) Template structure: headers, body, footer, buttons, variables, and practical limits. * [message\_template\_quality\_update Webhook](https://dualhook.com/docs/webhook-message-template-quality-update) Field reference for WhatsApp message\_template\_quality\_update webhooks: template quality score changes, payload shape, and Dualhook handling. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WABA Integration Flow: Token Validation, Discovery and Webhook Subscription | Dualhook Browse Docs WABA Integration ================ End-to-end WhatsApp Business Account integration: token validation, discovery, and subscription. Looking for the high-level overview first? Start with [WhatsApp Business API](https://dualhook.com/whatsapp-business-api) and then return here for the implementation details. This is the server-side sequence Dualhook runs after Embedded Signup finishes: validate the customer-scoped token, discover the WhatsApp Business Account and phone numbers it grants, subscribe the app, and configure Webhook Override. It is documented so you can reason about what a "healthy" connection means in the dashboard, not because you need to reproduce it. Customers never receive the Meta token. Overview[](https://dualhook.com/docs/waba-integration#overview) ---------------------------------------------------------------- This describes the server-side [WABA](https://dualhook.com/docs/whatsapp-business-accounts) integration model that Dualhook performs. Customers do not receive Dualhook's Meta authorization token and should use the dashboard for management operations. Required Inputs[](https://dualhook.com/docs/waba-integration#required-inputs) ------------------------------------------------------------------------------ * `BUSINESS_ID` — from your [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) * `WABA_ID` — from your [WhatsApp Business Account](https://dualhook.com/docs/whatsapp-business-accounts) * `PHONE_NUMBER_ID` — from your [phone number](https://dualhook.com/docs/phone-numbers) * Dualhook's reviewed Meta app and encrypted customer-scoped BISU token * Webhook URL + verify token Integration Flow[](https://dualhook.com/docs/waba-integration#integration-flow) -------------------------------------------------------------------------------- 1. Validate token and permissions. 2. Discover WABA and phone number IDs. 3. Subscribe app and configure [Webhook Override](https://dualhook.com/docs/webhook-override) . 4. Send a test message. 5. Verify [webhook delivery](https://dualhook.com/docs/messaging-webhook) and statuses. Validate Access Token[](https://dualhook.com/docs/waba-integration#validate-access-token) ------------------------------------------------------------------------------------------ Dualhook validates the encrypted token server-side and exposes only the resulting permission and lifecycle status in the connection Health tab. The token itself is never returned. List WABAs for a Business[](https://dualhook.com/docs/waba-integration#list-wabas-for-a-business) -------------------------------------------------------------------------------------------------- Dualhook discovers the WABAs granted during Embedded Signup and presents them in the connection selection UI if Meta returns more than one candidate. { "data": [\ { "id": "102290129340398", "name": "Acme Primary WABA" }\ ] } List Phone Numbers[](https://dualhook.com/docs/waba-integration#list-phone-numbers) ------------------------------------------------------------------------------------ Dualhook lists the phone numbers under each granted WABA server-side and presents the candidates without exposing Meta credentials. { "data": [\ {\ "id": "123456789012345",\ "display_phone_number": "15550001234",\ "verified_name": "Acme Inc",\ "name_status": "APPROVED",\ "quality_rating": "GREEN"\ }\ ] } Subscribe and Configure Override[](https://dualhook.com/docs/waba-integration#subscribe-and-configure-override) ---------------------------------------------------------------------------------------------------------------- Dualhook performs the `/{WABA_ID}/subscribed_apps` call server-side with the encrypted BISU token and the callback URL and verify token configured in the dashboard. { "success": true } Validation Checklist[](https://dualhook.com/docs/waba-integration#validation-checklist) ---------------------------------------------------------------------------------------- * Token is valid and has required scopes. * Correct `WABA_ID` and `PHONE_NUMBER_ID` are selected. * `subscribed_apps` returns success. * Incoming message webhooks arrive at your endpoint. * Outbound status updates are received for test sends. Dualhook sets and maintains webhook override configuration automatically. Message traffic remains Meta → your endpoint directly. Related ------- * [WhatsApp Business Accounts](https://dualhook.com/docs/whatsapp-business-accounts) What a WABA is, how it relates to phone numbers, and how ownership works. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Quick Start](https://dualhook.com/docs/getting-started) Set up Dualhook and connect your first WhatsApp Business Account in minutes. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 131049: Causes & How to Fix It | Dualhook Browse Docs WhatsApp Error 131049: Healthy Ecosystem Engagement =================================================== WhatsApp error 131049 means Meta withheld a marketing message to maintain healthy ecosystem engagement. Causes, the per-user limit, and how to fix it. > **TL;DR:** WhatsApp error `131049` means Meta chose not to deliver your **marketing** template message to protect the user from too many marketing messages (the per-user marketing frequency cap). It is not a fault in your number or account. Fix it by waiting before retrying, using the correct template category for content that is genuinely non-marketing, and targeting only engaged users. What Error 131049 Means[](https://dualhook.com/docs/whatsapp-error-131049#what-error-131049-means) --------------------------------------------------------------------------------------------------- Meta's `details` string is: **"This message was not delivered to maintain healthy ecosystem engagement."** It is triggered by Meta's per-user marketing template message limits, an adaptive cap on how many marketing-category templates a single WhatsApp user receives across **all businesses** in a rolling window. Meta does not publish a fixed number. In addition, Meta's guidance indicates that marketing template messages to WhatsApp users in some regions (notably the **United States**) are not delivered and return `131049`; confirm current regional rules in Meta's per-user-limits doc before relying on this. Where You See It[](https://dualhook.com/docs/whatsapp-error-131049#where-you-see-it) ------------------------------------------------------------------------------------- Most often it arrives asynchronously in the `statuses[].errors[]` array of the `messages` webhook (`status: failed`, `code: 131049`). It can also be returned synchronously on the send call. A `200` on the send call is **not** proof of delivery here. Common Causes[](https://dualhook.com/docs/whatsapp-error-131049#common-causes) ------------------------------------------------------------------------------- * The recipient already received marketing messages (from your business or others) in the current window. * The template is categorized as **Marketing**, even when your team thinks of it as informational. * Low engagement: the user rarely reads or replies to marketing messages. * Sending marketing templates into a region where Meta currently restricts them. How to Fix It[](https://dualhook.com/docs/whatsapp-error-131049#how-to-fix-it) ------------------------------------------------------------------------------- 1. **Do not immediately retry** — the same error returns while the limit is in effect. 2. Wait before retrying. Meta's support guidance is to wait **at least 24 hours** when this is caused by per-user marketing limits. 3. If the content is **genuinely** utility or authentication under Meta's template-category rules, use the correct non-marketing template category (those categories are not subject to the marketing cap). Do **not** recategorize promotional content as utility just to bypass 131049 — miscategorization is a policy/quality risk that can hurt your account. 4. Verify the template's category is correct; recategorize only where a utility/auth message was genuinely mislabeled Marketing. 5. Re-engage low-intent users with value-driven content so future deliverability improves. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-131049#how-to-prevent-it) --------------------------------------------------------------------------------------- * Segment to engaged users; suppress cold contacts. * Space marketing sends; avoid multiple marketing pushes per user per day. * Keep template quality and opt-in hygiene high. * Monitor failed-status rates to detect cap onset early. Related Errors[](https://dualhook.com/docs/whatsapp-error-131049#related-errors) --------------------------------------------------------------------------------- * [`/docs/whatsapp-error-131050`](https://dualhook.com/docs/whatsapp-error-131050) — explicit marketing opt-out * [`/docs/whatsapp-error-131048`](https://dualhook.com/docs/whatsapp-error-131048) — phone-number spam-rate restriction * [`/docs/whatsapp-error-131026`](https://dualhook.com/docs/whatsapp-error-131026) — undeliverable * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-131049#faq) ----------------------------------------------------------- **What does WhatsApp error 131049 mean?** Meta withheld your marketing template to maintain a healthy ecosystem — the recipient hit the cross-business per-user marketing cap. **How do I fix 131049?** Wait before retrying (Meta says at least 24 hours for per-user limits), use the correct non-marketing template category for content that genuinely qualifies as utility/auth, and target engaged users. **Why do invoices/reminders get 131049?** Usually because the template is categorized as Marketing even though the content feels transactional. **Is 131049 my fault?** No — it is recipient-level and cross-business; even a first message can fail. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-131049#how-dualhook-helps) ----------------------------------------------------------------------------------------- This is a **send-side** error: the fix lives in your sending backend. Dualhook relays `/messages` requests without content persistence and returns Meta's response unchanged. It also surfaces failed delivery statuses routed via your webhook. Retry and throttling decisions remain in your backend; Dualhook never retries sends automatically. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 130497: Country Restriction Fix | Dualhook Browse Docs WhatsApp Error 130497: Country Restriction ========================================== WhatsApp error 130497 means your WABA is restricted from messaging users in certain countries. Causes, the scaling path, and Brazil/Indonesia limits. > **TL;DR:** WhatsApp error `130497` means your WhatsApp Business Account is restricted from messaging users in certain countries. Businesses can message only countries they're approved for; cross-country messaging unlocks by completing a scaling path to the 2,000 messaging-limit tier — though Brazil and Indonesia may stay restricted even then. What Error 130497 Means[](https://dualhook.com/docs/whatsapp-error-130497#what-error-130497-means) --------------------------------------------------------------------------------------------------- Meta's `details` string is: **"The WhatsApp Business Account is restricted from messaging to users in certain countries."** (Integrity error — confirmed verbatim on the live error table.) The error page's own guidance points to the WhatsApp Business Messaging Policy for the countries allowed in your business category. Meta's support material has additionally indicated that cross-country messaging can unlock as your account scales messaging tiers, that it can take time to take effect after you qualify, and that some countries (notably **Brazil and Indonesia**) may remain restricted for non-local senders even then. **Confirm the current scaling-path requirements, any specific tier number/timeline, and country-specific exceptions in Meta's live support docs before quoting them to a customer** — those figures change and were not re-verified for this page. Where You See It[](https://dualhook.com/docs/whatsapp-error-130497#where-you-see-it) ------------------------------------------------------------------------------------- A send-time failure (`code: 130497`) when targeting a restricted country; depending on tooling and timing it can also surface in operational logs. Teams usually notice it when sends to one country fail while others succeed. Common Causes[](https://dualhook.com/docs/whatsapp-error-130497#common-causes) ------------------------------------------------------------------------------- * Sending to a country your WABA isn't approved for. * Cross-border sending before reaching the required messaging tier. * Business category prohibited in the target country. * Brazil / Indonesia anti-fraud restrictions for non-local senders. How to Fix It[](https://dualhook.com/docs/whatsapp-error-130497#how-to-fix-it) ------------------------------------------------------------------------------- 1. Confirm the failure pattern is country-specific (collect examples by country, timestamp, `fbtrace_id`). 2. Check the WhatsApp Business Messaging Policy for allowed countries in your category. 3. Review Meta's current scaling-path / messaging-tier requirements for unlocking cross-country messaging (confirm the exact tier and timeline in Meta's live docs). 4. Confirm business verification and complete Business Manager profile/address fields. 5. For Brazil/Indonesia, expect cross-country sending may remain blocked; escalate to Meta with examples rather than retrying blindly. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-130497#how-to-prevent-it) --------------------------------------------------------------------------------------- * Map which countries your WABA is approved for before campaigns. * Test new geographies early, before launch volume. * Keep business verification and profile data complete. Related Errors[](https://dualhook.com/docs/whatsapp-error-130497#related-errors) --------------------------------------------------------------------------------- * [`/docs/whatsapp-error-131031`](https://dualhook.com/docs/whatsapp-error-131031) — broader account restriction * [`/docs/whatsapp-error-131026`](https://dualhook.com/docs/whatsapp-error-131026) — undeliverable recipient * [`/docs/whatsapp-error-131048`](https://dualhook.com/docs/whatsapp-error-131048) — spam-rate restriction * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-130497#faq) ----------------------------------------------------------- **What is WhatsApp error 130497?** Your account can't message users in certain countries. **How do I unlock cross-country messaging?** Meta's guidance ties it to scaling your messaging tier; check the current requirements and timeline in Meta's live support docs. **Can I message Brazil/Indonesia after scaling?** Possibly not — Meta says it may remain blocked. **Will business verification alone fix it?** Not necessarily — this is about messaging eligibility to the destination country, not just verification. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-130497#how-dualhook-helps) ----------------------------------------------------------------------------------------- This is an **account-level** restriction where Dualhook adds visibility, not the fix. Dualhook surfaces WABA account-status and review signals through its monitoring views so a country restriction or category issue is visible early. See [Maintain Account Health](https://dualhook.com/docs/account-health) and [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) . Lifting the restriction happens through Meta (scaling path, verification, policy compliance). [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 131026: Message Undeliverable Fix | Dualhook Browse Docs WhatsApp Error 131026: Message Undeliverable ============================================ WhatsApp error 131026 means the message is undeliverable — not a WhatsApp number, unaccepted Terms, or an outdated app. Causes and how to fix it. > **TL;DR:** WhatsApp error `131026` means the message could not be delivered — usually because the recipient is not a valid WhatsApp user, has not accepted WhatsApp's latest Terms, or is on an outdated app version. You cannot force delivery; verify the number and reach the user another way. What Error 131026 Means[](https://dualhook.com/docs/whatsapp-error-131026#what-error-131026-means) --------------------------------------------------------------------------------------------------- Meta's `details` lists the reasons: **"Unable to deliver message. Reasons can include: The recipient phone number is not a WhatsApp phone number. Recipient has not accepted our new Terms of Service and Privacy Policy. Recipient using an old WhatsApp version; must use the following WhatsApp version or greater: Android 2.21.15.15, SMBA 2.21.15.15, iOS 2.21.170.4, SMBI 2.21.170.4, KaiOS 2.2130.10, Web 2.2132.6."** Older community wording ("Receiver is incapable of receiving this message") refers to the same undeliverable family. Sending an **authentication** template to an Indian (+91) number can also surface delivery failures, as auth templates currently cannot be sent to users in India. Where You See It[](https://dualhook.com/docs/whatsapp-error-131026#where-you-see-it) ------------------------------------------------------------------------------------- Both ways: as an **immediate API error**, and as a **failed status webhook** in `statuses[].errors[]` after the send initially returned `200` + a `wamid`. A `200` is not proof of final delivery — trust the failed status webhook as the final verdict. Common Causes[](https://dualhook.com/docs/whatsapp-error-131026#common-causes) ------------------------------------------------------------------------------- * Recipient number is not registered on WhatsApp. * Recipient never accepted the latest Terms of Service. * Recipient app version is below the minimum. * Policy/engagement signals (especially for marketing templates); Meta may not disclose a more specific private reason. How to Fix It[](https://dualhook.com/docs/whatsapp-error-131026#how-to-fix-it) ------------------------------------------------------------------------------- 1. Verify the destination number is valid and active on WhatsApp, formatted in E.164. 2. On another channel, ask the user to message your business, update WhatsApp, and accept the latest Terms. 3. If the API returned `200` but the webhook later failed with `131026`, treat the webhook as authoritative. 4. Don't hammer-retry; treat persistent 131026 as an unreachable contact. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-131026#how-to-prevent-it) --------------------------------------------------------------------------------------- * Validate and normalize numbers before queuing sends. * Collect explicit opt-in so contacts are real, reachable users. * Process failed status webhooks, not just immediate responses. * Suppress repeatedly-undeliverable numbers; see [Meta BSUID & Contact Book Transition](https://dualhook.com/docs/contacts-bsuid) for contact-validation patterns. Related Errors[](https://dualhook.com/docs/whatsapp-error-131026#related-errors) --------------------------------------------------------------------------------- * [`/docs/whatsapp-error-131047`](https://dualhook.com/docs/whatsapp-error-131047) — outside the 24-hour window * [`/docs/whatsapp-error-131000`](https://dualhook.com/docs/whatsapp-error-131000) — generic send failure * [`/docs/whatsapp-error-130497`](https://dualhook.com/docs/whatsapp-error-130497) — country restriction * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-131026#faq) ----------------------------------------------------------- **What is WhatsApp error 131026?** A delivery failure — bad number, unaccepted Terms, or outdated app. **Why did the API return 200 but the message fail?** Acceptance and delivery are different stages; the final verdict can arrive later in the status webhook. **Can I force delivery on 131026?** No. Verify the number and use another channel. **Does 131026 always mean an invalid number?** No — that's one cause among several. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-131026#how-dualhook-helps) ----------------------------------------------------------------------------------------- This is a **send-side** error. Dualhook relays your outbound payload without modifying or retaining its content and cannot deliver a message WhatsApp will not accept. Because message-path webhooks go directly from Meta to your endpoint, your backend must log and act on the final failed status; Dualhook surfaces 131026 rates so you can quantify and suppress unreachable contacts via [Messaging Health Status](https://dualhook.com/docs/health-monitoring) . [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Webhook Error 100: Subscribe Before Override | Dualhook Browse Docs WhatsApp Webhook Error 100: "Before Override the Current Callback URI…" ======================================================================= "(#100) Before override the current callback uri" means your app must subscribe to the WABA messages field first. The two-step fix, plus webhook 2200/2201. > **TL;DR:** This error means you tried to set a per-WABA `override_callback_uri` before your app was subscribed to that WhatsApp Business Account's `messages` webhook. The fix is a two-step flow: first subscribe the app to the WABA, then override the callback URI. It is returned as Graph code **100** (OAuthException). What This Error Means[](https://dualhook.com/docs/whatsapp-error-100-webhook-override#what-this-error-means) ------------------------------------------------------------------------------------------------------------- The exact message is: **"(#100) Before override the current callback uri, your app must be subscribed to receive messages for WhatsApp Business Account."** On Meta's error table, code `100` is the generic "request included one or more unsupported or misspelled parameters" OAuthException; this override case is a specific code-100 message. Meta requires the app to be subscribed to the WABA (via `POST /{waba-id}/subscribed_apps`) before you can set or override its callback URI — a documented two-step sequence. Where You See It[](https://dualhook.com/docs/whatsapp-error-100-webhook-override#where-you-see-it) --------------------------------------------------------------------------------------------------- An **immediate API error** (HTTP 400) when calling `/{waba-id}/subscribed_apps` with `override_callback_uri` while not yet subscribed. Common Causes[](https://dualhook.com/docs/whatsapp-error-100-webhook-override#common-causes) --------------------------------------------------------------------------------------------- * Attempting subscription + callback override in a single call. * Skipping the initial `subscribed_apps` subscription step. * Missing `whatsapp_business_management` / `whatsapp_business_messaging` permissions. How to Fix It[](https://dualhook.com/docs/whatsapp-error-100-webhook-override#how-to-fix-it) --------------------------------------------------------------------------------------------- 1. First `POST /{waba-id}/subscribed_apps` to subscribe your app to the WABA. 2. Then set the `override_callback_uri` (and `verify_token`) in a second step. 3. Ensure your app has `whatsapp_business_management` and `whatsapp_business_messaging`. 4. Confirm the `messages` webhook field is subscribed. Webhook Verification Errors 2200 and 2201[](https://dualhook.com/docs/whatsapp-error-100-webhook-override#webhook-verification-errors-2200-and-2201) ----------------------------------------------------------------------------------------------------------------------------------------------------- > **Note:** `2200`/`2201` are Graph-webhook **verification** errors seen during callback setup. They are not part of the WhatsApp Cloud API error-code table; the exact wording below is reported by practitioners and in Graph webhook contexts — verify against your own logs. * **2200 — Callback/subscription verification failed:** Meta's GET verification to your callback URL failed (commonly "HTTP Status Code = 404 / Not Found", "Request Timeout / 408", or SSL/cURL errors). Your endpoint didn't respond correctly to the verification request. * **2201 — Invalid `hub.challenge`:** your endpoint responded but didn't echo back the exact `hub.challenge` value (e.g. it returned JSON or HTML instead of the raw token). To pass verification, on the GET request confirm `hub.mode == subscribe` and `hub.verify_token` matches, then return the **raw** `hub.challenge` value with **HTTP 200** — no JSON/HTML envelope. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-100-webhook-override#how-to-prevent-it) ----------------------------------------------------------------------------------------------------- * Always subscribe first, override second. * Keep the callback endpoint publicly reachable over HTTPS and fast (avoid timeouts); never point Meta at localhost or private URLs. * Exempt the verification path from aggressive rate limiting/proxy rewriting. Related Errors[](https://dualhook.com/docs/whatsapp-error-100-webhook-override#related-errors) ----------------------------------------------------------------------------------------------- * [`/docs/whatsapp-error-190`](https://dualhook.com/docs/whatsapp-error-190) — token expired during setup * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) * [`/docs/whatsapp-error-80007`](https://dualhook.com/docs/whatsapp-error-80007) — WABA management rate limit * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-100-webhook-override#faq) ------------------------------------------------------------------------- **What does "Before override the current callback uri" mean?** Subscribe your app to the WABA first, then override the callback URI. **What error code is it?** Graph code 100 (OAuthException). **What are 2200 and 2201?** Graph-webhook verification errors — 2200 is a callback/verification failure, 2201 is a `hub.challenge` mismatch. They're not in Meta's WhatsApp Cloud API error table. **Can I return JSON like `{ "challenge": "123" }`?** No — return the raw challenge value with HTTP 200. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-100-webhook-override#how-dualhook-helps) ------------------------------------------------------------------------------------------------------- This is the core of what Dualhook does, so the fix is largely handled for you. Dualhook connects your WABA via Meta Embedded Signup and manages the subscribe-then-override sequence on `override_callback_uri`, routing inbound message-path webhooks straight to your endpoint without receiving or storing them. Supported outbound requests use Dualhook's separate no-retention runtime. Dualhook surfaces subscription state and webhook health so you can see if a WABA is unsubscribed or a callback verification is failing. See [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) and [Messaging Health Status](https://dualhook.com/docs/health-monitoring) . [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 131008: Fix Missing Required Parameter | Dualhook Browse Docs WhatsApp Error 131008: Required Parameter Is Missing ==================================================== WhatsApp error 131008 means the request is missing a required parameter — common with flow buttons and dynamic template buttons. Causes and step-by-step fixes. > **TL;DR:** WhatsApp error 131008 means your API request left out a parameter the endpoint requires. Meta's `details` string is **"The request is missing a required parameter."** It is a classic payload-construction mistake — most often a missing flow button component or an empty value for a dynamic button or template variable. Fix it by adding the exact field the endpoint reference requires. What Error 131008 Means[](https://dualhook.com/docs/whatsapp-error-131008#what-error-131008-means) --------------------------------------------------------------------------------------------------- 131008 is a request-validation error. The endpoint expected a field you didn't send. Meta's official `details` value is: **"The request is missing a required parameter."** The runtime `details` often names the missing field. Two widely reported variants (community-observed, not the canonical Meta string) are `Please specify a flow button component within template['components']` — captured verbatim in a GitHub discussion on the Whatsapp-Business-Cloud-Api-Net library, which appears when sending a flow template without its flow button — and `Parameter of type text is missing text value` when a body variable is present but empty. Where You See It[](https://dualhook.com/docs/whatsapp-error-131008#where-you-see-it) ------------------------------------------------------------------------------------- 131008 is **synchronous**. It is returned in the Graph API HTTP response (`400 Bad Request`) when the request is rejected before acceptance. It does not appear in a `statuses[].errors[]` status webhook, because no message was created. Some BSP layers surface it as a 403; the underlying Meta condition is a missing required parameter. Common Causes[](https://dualhook.com/docs/whatsapp-error-131008#common-causes) ------------------------------------------------------------------------------- * Sending a **flow template** without the required `button` component of `sub_type: flow` in `template.components`. * A **dynamic URL/button** template that requires a button parameter, but the parameter array omits it. * A template body variable is included but its `text` value is empty/null because an upstream CRM field wasn't populated. * Omitting a required top-level field such as `to`, `type`, or `messaging_product`. * Interactive messages missing a required sub-object (e.g. `action`, `button`, or list `sections`). How to Fix It[](https://dualhook.com/docs/whatsapp-error-131008#how-to-fix-it) ------------------------------------------------------------------------------- 1. Read the `error_data.details` string — it usually names the missing parameter or component. 2. Check the **endpoint reference** for the message/template type you're sending and list every required field. For sending a message these commonly include `messaging_product: "whatsapp"`, `to` (E.164), `type`, and the object matching `type` (e.g. a `template` object with `name` and `language`). 3. For **flow templates**, add the flow button component, e.g. a `button` component with `sub_type: "flow"` and its required `parameters`. 4. For **dynamic buttons**, include the button parameter object in `components` with the correct `index` and `sub_type`. 5. Add a guard in your code: if any CRM-sourced variable is empty, skip or substitute before sending so you never transmit an empty required value. 6. Re-send and confirm a `200` with a message ID. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-131008#how-to-prevent-it) --------------------------------------------------------------------------------------- * Validate payloads against a schema for each message type before calling Meta. * Add conditional checks for every personalization variable and alert/skip when a value is missing, rather than sending blanks. * Generate flow/button components from the template definition fetched via the Message Template API. Related Errors[](https://dualhook.com/docs/whatsapp-error-131008#related-errors) --------------------------------------------------------------------------------- * [WhatsApp Error 132012](https://dualhook.com/docs/whatsapp-error-132012) — parameter present but wrong format/type. * [WhatsApp Error 132000](https://dualhook.com/docs/whatsapp-error-132000) — parameter count mismatch. * [WhatsApp Webhook Override (Error 100)](https://dualhook.com/docs/whatsapp-error-100-webhook-override) * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-131008#faq) ----------------------------------------------------------- **Q: I'm sending a flow template and get "Please specify a flow button component" — why?** A: A flow template requires a flow button component inside `template.components`. Add the `button` component of `sub_type: flow` with its parameters. **Q: My variable is in the payload but I still get 131008 — why?** A: The variable is likely present with an **empty** value (e.g. `Parameter of type text is missing text value`). Populate or remove it. **Q: Is 131008 the same as 132000?** A: No. 131008 = a required parameter is **absent**; 132000 = the **count** of provided values doesn't match the template's variables. **Q: Does 131008 come back through a webhook?** A: No, it's synchronous in the API response. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-131008#how-dualhook-helps) ----------------------------------------------------------------------------------------- Honest answer: 131008 is fixed in **your sending backend** because the submitted payload is missing a field. Dualhook relays the request without modifying its message body. Dualhook's relevant contribution is making the template/flow structure visible via its `whatsapp_business_management` access ([Templates](https://dualhook.com/docs/templates) , [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) ) so you can see which components (including flow buttons) a template requires before you build the request. Always log `error_data.details` in your sender; for 131008 it usually names the exact missing parameter. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 131056: 1 Message Every 6 Seconds Pair Rate Limit | Dualhook Browse Docs WhatsApp Error 131056: Pair Rate Limit Hit ========================================== WhatsApp error 131056 means too many messages to the same recipient too fast (pair rate limit). How to pace per-recipient sends, back off, and prevent it. > **TL;DR:** WhatsApp error `131056` ("pair rate limit hit") means you sent too many messages from your business number to the **same** recipient in a short period. Meta's rule of thumb is **1 message every 6 seconds to the same WhatsApp user**. It's not an account block — pace your per-recipient sends and back off; you can still message other numbers immediately. What Error 131056 Means[](https://dualhook.com/docs/whatsapp-error-131056#what-error-131056-means) --------------------------------------------------------------------------------------------------- Meta's `details` string is: **"Too many messages sent from the sender phone number to the same recipient phone number in a short period of time."** (Throttling error — confirmed verbatim on the live table; the full message form is "(#131056) … pair rate limit hit", HTTP 429.) It's a per-pair safeguard distinct from account-wide throughput. Meta's solution text: "Wait and retry the operation, if you intend to send messages to the same phone number. You can still send messages to a different phone number without waiting." Where You See It[](https://dualhook.com/docs/whatsapp-error-131056#where-you-see-it) ------------------------------------------------------------------------------------- An **immediate API error** (HTTP 429) on the send call; it can also appear in tool error dashboards. The root condition is send-time per-pair throttling. Common Causes[](https://dualhook.com/docs/whatsapp-error-131056#common-causes) ------------------------------------------------------------------------------- * Rapid back-to-back messages to one recipient. * Multiple separate messages (confirmation + tracking + survey) fired together. * Retry loops or several workers racing to message the same user. How to Fix It[](https://dualhook.com/docs/whatsapp-error-131056#how-to-fix-it) ------------------------------------------------------------------------------- 1. Pause and retry that recipient with backoff. Meta's guidance is "wait and retry"; Dualhook's reference additionally recommends a `4^X`\-seconds backoff per retry **(this is a Dualhook recommendation, not Meta's wording)**. 2. Keep sending to other recipients — only the pair is throttled. 3. Bundle multiple updates into fewer, richer messages. 4. Audit parallel workers so only one drains a given recipient queue at a time. The 1 Message Every 6 Seconds Rule[](https://dualhook.com/docs/whatsapp-error-131056#the-1-message-every-6-seconds-rule) ------------------------------------------------------------------------------------------------------------------------- Meta's platform documentation states that a business phone number can send **1 message every 6 seconds to the same WhatsApp user**, which works out to about 10 messages per minute and 600 per hour for one business-to-user pair. This is independent of the number's overall throughput of 80 messages per second, and independent of the portfolio messaging limit. Bursting is allowed: up to **45 messages in a 6-second burst** to the same user, but the burst borrows against future quota. After a burst of 20, expect to wait roughly 2 minutes before that recipient accepts more. Designs that rely on "it worked once" are the ones that hit `131056` under load. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-131056#how-to-prevent-it) --------------------------------------------------------------------------------------- * Queue messages **per business-number/recipient pair** (a token bucket per pair) sized to the 1-per-6-seconds rule above. * Make retries pair-aware, not only globally rate-limited. See [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) for the throughput and portfolio limits that sit alongside it. Related Errors[](https://dualhook.com/docs/whatsapp-error-131056#related-errors) --------------------------------------------------------------------------------- * [`/docs/whatsapp-error-130429`](https://dualhook.com/docs/whatsapp-error-130429) — per-number throughput limit * [`/docs/whatsapp-error-131048`](https://dualhook.com/docs/whatsapp-error-131048) — spam-rate restriction * [`/docs/whatsapp-error-80007`](https://dualhook.com/docs/whatsapp-error-80007) — WABA management rate limit * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-131056#faq) ----------------------------------------------------------- **What is WhatsApp error 131056?** Too many messages to the same user too quickly. **Is my account blocked?** No — only the specific business↔user pair is throttled. **Is 131056 the same as 130429?** No — `131056` is recipient-pair specific; `130429` is broader send throughput. **Will generic exponential backoff fix it?** Only if retries are keyed to the same recipient pair; otherwise parallel workers recreate the problem. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-131056#how-dualhook-helps) ----------------------------------------------------------------------------------------- This is a **send-side** pacing problem fixed in your backend caller. Dualhook relays each requested send once and never automatically retries it, so your application must pace or retry deliberately. Dualhook documents the per-recipient limit and back-off guidance in [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) and surfaces failed statuses for visibility. Related ------- * [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) API rate limits, messaging limit tiers, quality rating, and scaling guidance. * [WhatsApp Error 130429: Throughput Limit Hit](https://dualhook.com/docs/whatsapp-error-130429) WhatsApp error 130429 means your send rate exceeded the per-number throughput Meta allows (default ~80 msg/sec). How to queue, shard, and back off safely. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 4: Fix App API Call Rate Limit | Dualhook Browse Docs WhatsApp Error 4: App Has Reached Its API Call Rate Limit ========================================================= WhatsApp error 4 means the app has reached its API call rate limit (Graph app-level, not message throughput). How to back off, batch, and prevent it. > **TL;DR:** WhatsApp error 4 is a throttling error meaning your Meta app has exceeded its **Graph API call rate limit** — the total number of API calls the app makes, not message throughput. Meta's `details` string is **"The app has reached its API call rate limit."** Fix it with exponential backoff, caching, and reducing call frequency; the limit resets on a rolling window. What Error 4 Means[](https://dualhook.com/docs/whatsapp-error-4#what-error-4-means) ------------------------------------------------------------------------------------ Error 4 is an **application-level** rate limit on the Graph API. It counts all API calls your app makes (template fetches, phone-number/WABA management calls, status checks, etc.) — distinct from message **throughput** (130429) and from WABA-level (80007) and per-pair (131056) limits. Meta's official `details` value is: **"The app has reached its API call rate limit."** Per Meta's "About the WhatsApp Business Platform" documentation, the default budget is **200 requests per hour, per app, per WABA**, and **5,000 requests per hour** for active WABAs with at least one registered phone number. Where You See It[](https://dualhook.com/docs/whatsapp-error-4#where-you-see-it) -------------------------------------------------------------------------------- **Synchronous** Graph API response (`400 Bad Request`). The current usage is visible in the **App Dashboard → Application Rate Limit** section. It is not a per-message status webhook event. Common Causes[](https://dualhook.com/docs/whatsapp-error-4#common-causes) -------------------------------------------------------------------------- * Polling endpoints (template lists, phone-number status, WABA state) too frequently instead of using webhooks. * Bursty management operations during a campaign (e.g. creating/fetching many templates at once). * Not caching responses that rarely change, so the same endpoint is hit repeatedly. * Many parallel workers all calling the Graph API without a shared rate budget. How to Fix It[](https://dualhook.com/docs/whatsapp-error-4#how-to-fix-it) -------------------------------------------------------------------------- 1. Open **App Dashboard → your app → Application Rate Limit** and confirm the app is at its limit. 2. Implement **exponential backoff with jitter**: on a 4, wait ~1s and double each retry. 3. **Cache** responses for slow-changing data (templates, phone numbers) instead of refetching. 4. **Batch** and **space out** non-urgent calls; avoid bursts. 5. Where you have multiple numbers, distribute load; wait for the rolling window to reset, then retry. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-4#how-to-prevent-it) ---------------------------------------------------------------------------------- * Replace polling with **webhooks** for template, phone-number, and account status updates. * Keep a local cache/store of template definitions and refresh only on change events. * Add a shared client-side rate limiter so concurrent workers respect one budget. Related Errors[](https://dualhook.com/docs/whatsapp-error-4#related-errors) ---------------------------------------------------------------------------- * [WhatsApp Error 130429](https://dualhook.com/docs/whatsapp-error-130429) — Cloud API message **throughput** reached. * [WhatsApp Error 80007](https://dualhook.com/docs/whatsapp-error-80007) — WABA-level rate limit. * [WhatsApp Error 131056](https://dualhook.com/docs/whatsapp-error-131056) — per-recipient pair rate limit. * [WhatsApp Webhook Override (Error 100)](https://dualhook.com/docs/whatsapp-error-100-webhook-override) * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-4#faq) ------------------------------------------------------ **Q: Is error 4 the same as 130429?** A: No. Error 4 is **app-level API call** frequency; 130429 is **message throughput** (messages per second). **Q: What are the actual limits?** A: Per Meta, 200 requests/hour per app per WABA by default, and 5,000 requests/hour for active WABAs with at least one registered phone number. **Q: Can I raise the app rate limit?** A: Graph app rate limits generally can't be manually increased — reduce/optimize call frequency instead. **Q: Does error 4 stop message sending?** A: It blocks API calls while over the limit, which can interrupt sends and management operations until the window resets. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-4#how-dualhook-helps) ------------------------------------------------------------------------------------ This is a platform/throttling code where Dualhook helps meaningfully. Because Dualhook holds advanced `whatsapp_business_management` access and pushes account/template/phone-number state via **webhooks**, it reduces your need to poll Meta — the single biggest driver of error 4. Use [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) and [Messaging Health Status](https://dualhook.com/docs/health-monitoring) so status changes arrive as events instead of forcing repeated Graph calls from your app. Dualhook also documents backoff/retry strategy in the [Meta API Error Reference](https://dualhook.com/docs/api-errors) . The retry/backoff logic in your own caller is still yours to implement. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 131042: Fix Payment/Eligibility Issue | Dualhook Browse Docs WhatsApp Error 131042: Business Eligibility / Payment Issue =========================================================== WhatsApp error 131042 means your account has a payment-method problem blocking sends. The full Meta cause list, how to restore billing eligibility, and prevention. > **TL;DR:** WhatsApp error 131042 means your WhatsApp Business Account cannot be billed for the message you tried to send. The message developers see is **"Message failed to send because there were one or more errors related to your payment method."** It blocks paid (business-initiated) and template sends until billing is fixed — usually by attaching an active payment method/credit line to the WABA, or correcting timezone, currency, or tax settings. What Error 131042 Means[](https://dualhook.com/docs/whatsapp-error-131042#what-error-131042-means) --------------------------------------------------------------------------------------------------- 131042 is an account/billing eligibility error. WhatsApp's paid messaging requires an active payment account on the WABA; when Meta cannot charge you, it refuses the send. The error message surfaced by Meta is: **"Message failed to send because there were one or more errors related to your payment method."** Meta's docs enumerate the common problems: payment account is not attached to the WhatsApp Business Account; credit line is over the limit; credit line (payment account) not set or active; WABA is deleted; WABA is suspended; timezone not set; currency not set; the "Messaging For" (On Behalf Of) request is pending or declined; the conversation free-tier threshold was exceeded without a valid payment method. Where You See It[](https://dualhook.com/docs/whatsapp-error-131042#where-you-see-it) ------------------------------------------------------------------------------------- 131042 typically arrives as a **synchronous** `/messages` API error, but because it is an account-state condition it can also surface on send attempts across your stack. It most often appears when sending business-initiated **template** messages (which are billable) rather than free-window service replies. It is not a payload error — the same request would succeed once billing is healthy. Common Causes[](https://dualhook.com/docs/whatsapp-error-131042#common-causes) ------------------------------------------------------------------------------- * A payment method exists in Meta Business Suite but is **not assigned at the WABA level** (the single most common practitioner cause). * The credit line is over its limit, inactive, or never set. * The WABA is deleted or suspended. * Timezone or currency was never configured on the account. * The "Messaging For" (On Behalf Of) request is pending or was declined. * The free-tier conversation threshold was exceeded with no valid payment method on file. * **Community-reported:** tax information has not been entered in Meta Business Manager where Meta requires it; or a small verification charge failed, leaving the account "ineligible." How to Fix It[](https://dualhook.com/docs/whatsapp-error-131042#how-to-fix-it) ------------------------------------------------------------------------------- 1. Go to **business.facebook.com → Business settings → WhatsApp Accounts**, select the affected WABA, and open **Billing / Payment settings**. 2. Confirm a valid payment method is **assigned to this specific WABA**, not just present in the portfolio. 3. Clear any outstanding dues and verify the credit line is active and under its limit. 4. Set **timezone** and **currency** if blank; add **tax info** where Meta requires it. 5. If sending on behalf of another business, ensure the **Messaging For / OBO** request is accepted. 6. Wait for eligibility to refresh and retry. Per Wetarseel's 131042 guide: "Once the card is correctly added, the error usually clears instantly. However, in some cases, it may take up to 24 hours for Meta's eligibility status to refresh." How to Prevent It[](https://dualhook.com/docs/whatsapp-error-131042#how-to-prevent-it) --------------------------------------------------------------------------------------- * Use a credit card that supports international recurring charges; verify no temporary holds are failing. * Monitor your account's billing/eligibility status proactively rather than discovering it on a failed campaign. * Keep the WABA out of suspension by maintaining policy and quality compliance. Related Errors[](https://dualhook.com/docs/whatsapp-error-131042#related-errors) --------------------------------------------------------------------------------- * [WhatsApp Error 368](https://dualhook.com/docs/whatsapp-error-368) — account restricted/disabled for policy violations. * [WhatsApp Error 131031](https://dualhook.com/docs/whatsapp-error-131031) — account locked (policy or data verification). * [WhatsApp Error 131064](https://dualhook.com/docs/whatsapp-error-131064) — messaging limit due to template classification violations. * [WhatsApp Webhook Override (Error 100)](https://dualhook.com/docs/whatsapp-error-100-webhook-override) * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-131042#faq) ----------------------------------------------------------- **Q: I already added a card — why do I still get 131042?** A: Adding a card to Business Suite is not enough; it must be the active payment method assigned to that WABA. Also check for outstanding dues or a failed verification charge. **Q: Is 131042 a billing problem with my BSP/provider?** A: No. It is your **WhatsApp/Meta** billing, separate from any provider's invoice. **Q: How long until it clears after I fix billing?** A: Often immediately; community reports (Wetarseel) indicate Meta's eligibility status can take up to ~24 hours to refresh. **Q: Why did marketing templates fail but service replies worked?** A: Service replies inside the customer window may be free; billable business-initiated templates require a chargeable payment method. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-131042#how-dualhook-helps) ----------------------------------------------------------------------------------------- This is an account-status code, so Dualhook adds real value. Using its advanced `whatsapp_business_management` access and account-level webhooks, Dualhook surfaces WABA connection and account-status signals so a billing/eligibility problem becomes visible in your monitoring views instead of as a surprise campaign failure. See [Maintain Account Health](https://dualhook.com/docs/account-health) and [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) . The actual payment fix still happens in Meta Business Suite — Dualhook cannot charge a card or assign a credit line on your behalf — but it shortens the time from "something broke" to "I know which WABA and why." [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 133016: Fix Too Many Register Attempts | Dualhook Browse Docs WhatsApp Error 133016: Too Many Registration Attempts ===================================================== WhatsApp error 133016 means registration/deregistration failed due to too many attempts for the number in a short period. Why it happens and how to recover. > **TL;DR:** WhatsApp error 133016 means a phone number hit its registration/deregistration attempt limit and is temporarily blocked. Meta's `details` string is **"Registration or Deregistration failed because there were too many attempts for this phone number in a short period of time"**. The only reliable fix is to stop retrying and wait until the number is unblocked. What Error 133016 Means[](https://dualhook.com/docs/whatsapp-error-133016#what-error-133016-means) --------------------------------------------------------------------------------------------------- 133016 is a **throttling/registration** error. To prevent abuse, Meta caps how often a single business phone number can be registered or deregistered in a short window. Meta's official `details` value is: **"Registration or Deregistration failed because there were too many attempts for this phone number in a short period of time"** Meta's page directs you to the "Limitations" section of the Registration document for the exact threshold rather than stating the number inline. Per Heltar's error guide, the practical limit is described as 10 register/deregister requests per business number within a 72-hour moving window, after which further attempts are blocked for the next 72 hours. (Treat that specific figure as community-reported, since Meta links to its Registration doc rather than printing a number on the error page.) Where You See It[](https://dualhook.com/docs/whatsapp-error-133016#where-you-see-it) ------------------------------------------------------------------------------------- **Synchronous** Graph API response (`400 Bad Request`) on the register/deregister endpoints — not a messaging webhook. It frequently appears during onboarding loops (e.g. a connection flow that retries registration). Common Causes[](https://dualhook.com/docs/whatsapp-error-133016#common-causes) ------------------------------------------------------------------------------- * Repeated register/deregister calls during troubleshooting or a misbehaving onboarding loop. * Migrating or reconnecting the same number multiple times in quick succession. * Automated retries on a failing registration without backoff. * Connecting a number through several tools/apps in a short period. How to Fix It[](https://dualhook.com/docs/whatsapp-error-133016#how-to-fix-it) ------------------------------------------------------------------------------- 1. **Stop retrying.** Each additional attempt can keep the number over the rolling-window threshold and delay recovery. 2. Wait until the number is unblocked — confirm the current window via Meta's Registration "Limitations." 3. Before re-attempting, fix the **root cause** of the original failures (e.g. wrong two-step PIN, missing display name) so you don't burn attempts again. 4. Retry the registration **once** after the window resets and the prerequisites are correct. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-133016#how-to-prevent-it) --------------------------------------------------------------------------------------- * Never auto-retry registration in a tight loop; require manual confirmation or wide backoff. * Resolve display-name approval and two-step PIN issues before attempting registration. * Avoid connecting the same number through multiple platforms simultaneously. Related Errors[](https://dualhook.com/docs/whatsapp-error-133016#related-errors) --------------------------------------------------------------------------------- * [WhatsApp Webhook Override (Error 100)](https://dualhook.com/docs/whatsapp-error-100-webhook-override) * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-133016#faq) ----------------------------------------------------------- **Q: How long is the number blocked?** A: Confirm via Meta's Registration "Limitations." Community guides (Heltar) describe a 72-hour block once the limit is hit. **Q: How many attempts trigger 133016?** A: Community guides cite 10 register/deregister requests per business number within a 72-hour moving window; Meta documents the exact limit in the Registration doc. **Q: Will waiting less than the window and retrying help?** A: No — retrying early keeps the number over the rolling-window threshold and delays recovery. Wait it out. **Q: I got 133016 while connecting via a tool — why?** A: The connection flow likely retried registration too many times. Pause, fix the underlying cause, then attempt once. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-133016#how-dualhook-helps) ----------------------------------------------------------------------------------------- This is a registration/account-state code where Dualhook helps. Dualhook onboards numbers through Meta **Embedded Signup** and surfaces phone-number and connection state via its advanced `whatsapp_business_management` access, so a number that's blocked or in a failed registration state is visible in monitoring — discouraging the blind retry loop that triggers 133016 in the first place. See [Phone Numbers](https://dualhook.com/docs/phone-numbers) , [Embedded Signup](https://dualhook.com/docs/embedded-signup) , and [Troubleshooting](https://dualhook.com/docs/troubleshooting) . Dualhook can't shorten Meta's cooldown window; what it does is give you a clear status so you wait correctly and re-attempt once, cleanly. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Calling Pricing | Dualhook Browse Docs Calling Pricing =============== How Meta bills WhatsApp Calling: free user-initiated calls, per-pulse business-initiated billing, volume tiers, customer service window interaction, call permission request charges, billing prerequisites. What This Page Covers[](https://dualhook.com/docs/calling-pricing#what-this-page-covers) ----------------------------------------------------------------------------------------- How Meta bills WhatsApp Business Calling. UIC is free; BIC is per-pulse billed; permission-request messages are billed under standard messaging rates. For Dualhook subscription pricing, see [Pricing](https://dualhook.com/docs/pricing) . Calling charges are paid to Meta directly — Dualhook does not add per-call fees and is not in the calling billing path. Headline Rules[](https://dualhook.com/docs/calling-pricing#headline-rules) --------------------------------------------------------------------------- | Direction | Billed? | | --- | --- | | User-initiated calls (UIC) | **Free** | | Business-initiated calls (BIC) | Per-pulse billed (see below) | | Call permission request messages (free-form or template) | Billed under standard messaging pricing | Calls extend the customer service window: when a WhatsApp user calls you (whether you accept or not), or when they accept your BIC, the **CSW timer starts or refreshes** — same effect as messaging. How BIC Is Priced[](https://dualhook.com/docs/calling-pricing#how-bic-is-priced) --------------------------------------------------------------------------------- Charges are based on three factors: 1. **Duration** — billed in 6-second pulses 2. **Country code** of the recipient's phone number 3. **Volume tier** — your portfolio's monthly minutes called determines which tier rate applies A valid payment method is required to place calls — this also applies to phone numbers that only intend to receive calls (since callbacks are BIC). The 6-Second Pulse[](https://dualhook.com/docs/calling-pricing#the-6-second-pulse) ----------------------------------------------------------------------------------- Calls are billed in 6-second increments. Fractional pulses round **up** — a 56-second call is 9.33 pulses, charged as **10 pulses** (60 seconds). Always invoke the [terminate endpoint](https://dualhook.com/docs/calling-business-initiated#terminate-a-call) when you end a call, even if you sent an RTCP BYE. Skipping terminate can result in inaccurate pricing. Volume Tiers[](https://dualhook.com/docs/calling-pricing#volume-tiers) ----------------------------------------------------------------------- Tiering accrual works **the same way as messaging** — see [Pricing → Volume Tiers](https://dualhook.com/docs/pricing#volume-tiers) . Calling tiers are separate from messaging tiers but use the same monthly aggregation logic. For calls that span pricing tiers (e.g. you cross from the 0–50,000 tier into the 50,001–250,000 tier mid-call), the **entire call is priced at the higher-volume (lower) tier rate**. This is favorable to the business — it doesn't split a single call's billing. CSW and Calling[](https://dualhook.com/docs/calling-pricing#csw-and-calling) ----------------------------------------------------------------------------- A call now opens or refreshes the customer service window: | Trigger | CSW effect | | --- | --- | | User messages you | CSW opens / refreshes | | User calls you (any outcome — accepted or not) | CSW opens / refreshes | | User accepts your BIC | CSW opens / refreshes | | You answer a call | No effect on its own (the user calling already opened the CSW) | | You place a BIC that the user rejects | No effect on CSW | This means accepting an inbound call is enough to also send any service-message type to that user inside the next 24 hours — you don't need a separate text message to open the window. Call Permission Request Messages[](https://dualhook.com/docs/calling-pricing#call-permission-request-messages) --------------------------------------------------------------------------------------------------------------- Permission requests sent as either free-form interactive messages or template messages are billed under **standard messaging pricing**. They count against the regular messaging rate card for the recipient's country and category. They are **not** included in the calling pulse pricing. This is a separate cost from the call itself. Even if the user never accepts and you never call, you've paid for the request message. Call Analytics[](https://dualhook.com/docs/calling-pricing#call-analytics) --------------------------------------------------------------------------- GET //?fields=call_analytics Returns cost, count of completed calls, and average call duration. Useful for cost reconciliation and per-period reporting. Granular details and date-range parameters are documented in Meta's call analytics reference. Where to Find Current Rates[](https://dualhook.com/docs/calling-pricing#where-to-find-current-rates) ----------------------------------------------------------------------------------------------------- Meta publishes per-country, per-tier rate cards on their developer site. **Always reference the official rates** — they update on Meta's standard pricing calendar (Jan 1, Apr 1, Jul 1, Oct 1). [https://developers.facebook.com/documentation/business-messaging/whatsapp/calling/pricing](https://developers.facebook.com/documentation/business-messaging/whatsapp/calling/pricing) We don't mirror the rate tables here — they go stale fast. Billing Prerequisites[](https://dualhook.com/docs/calling-pricing#billing-prerequisites) ----------------------------------------------------------------------------------------- * A credit line attached to the WABA is required to use the Calling API. * Business verification is **not** required for calling (same as messaging). * Calling API usage does **not** currently count toward messaging rate limits, but the 1,000 concurrent calls limit per phone number applies. Related ------- * [Cloud API Calling](https://dualhook.com/docs/calling) Voice calling on WhatsApp Business Cloud API: VoIP via WebRTC or SIP, business-initiated and user-initiated calls, prerequisites, availability, and integration patterns. * [Business-Initiated Calls](https://dualhook.com/docs/calling-business-initiated) Place outbound voice calls to WhatsApp users. POST /calls with action: connect, SDP offer/answer flow, call status webhooks (RINGING/ACCEPTED/REJECTED), terminate webhook with duration, biz\_opaque\_callback\_data tracking. * [Pricing](https://dualhook.com/docs/pricing) Dualhook subscription plans, what is included, and how pricing relates to Meta messaging fees. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 130403: Business Blocked the End User | Dualhook Browse Docs WhatsApp Error 130403: Business Has Blocked the End User ======================================================== WhatsApp error 130403 means your business has blocked the recipient on WhatsApp, so the message can't be delivered. Do not retry — unblock the user instead. > **TL;DR:** WhatsApp error 130403 means the message can't be delivered because **your** business has blocked that recipient on WhatsApp. Meta's `details` string is **"Unable to deliver the message. This business has blocked the end user on WhatsApp"**. Don't retry — unblock the user via the Block API to resume messaging them. What Error 130403 Means[](https://dualhook.com/docs/whatsapp-error-130403#what-error-130403-means) --------------------------------------------------------------------------------------------------- 130403 is a send/delivery error caused by your own block list: the business previously blocked this WhatsApp user, so outbound messages to them are refused. Meta's official `details` value is: **"Unable to deliver the message. This business has blocked the end user on WhatsApp"** Meta's guidance is explicit: **do not retry** — unblock the WhatsApp user to resume sending messages to them. (Note: this is _your business blocking the recipient_, not Meta blocking your account. A platform-level restriction on your account would surface as 368 or 131031, not 130403.) Where You See It[](https://dualhook.com/docs/whatsapp-error-130403#where-you-see-it) ------------------------------------------------------------------------------------- It can surface **synchronously** in the `/messages` response and/or asynchronously in the message **status webhook** (`statuses[].errors[]`) as a delivery failure. Either way the cause is the same: the recipient is on your business's block list. Common Causes[](https://dualhook.com/docs/whatsapp-error-130403#common-causes) ------------------------------------------------------------------------------- * The recipient was explicitly **blocked** via the Block API or in WhatsApp Manager. * A spam/abuse-prevention workflow auto-blocked the user earlier and you later tried to message them. * A bulk block operation captured a number you now legitimately need to message. How to Fix It[](https://dualhook.com/docs/whatsapp-error-130403#how-to-fix-it) ------------------------------------------------------------------------------- 1. **Do not retry** the send — it will keep failing while the block is in place. 2. **Unblock** the user using Meta's Block API (or in WhatsApp Manager). See Meta's "Unblock a user" guide. 3. Confirm the unblock succeeded, then resend. 4. If you didn't intend the block, review the workflow that created it to prevent recurrence. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-130403#how-to-prevent-it) --------------------------------------------------------------------------------------- * Keep your block/unblock workflow auditable so you know which numbers are blocked and why. * Avoid broad auto-block rules that can capture future legitimate recipients. * Reconcile your CRM "do not contact" list against your WhatsApp block list. Related Errors[](https://dualhook.com/docs/whatsapp-error-130403#related-errors) --------------------------------------------------------------------------------- * [WhatsApp Error 131026](https://dualhook.com/docs/whatsapp-error-131026) — message undeliverable (recipient-side reasons). * [WhatsApp Error 131047](https://dualhook.com/docs/whatsapp-error-131047) — re-engagement (24-hour window) required. * [WhatsApp Error 131050](https://dualhook.com/docs/whatsapp-error-131050) — user stopped marketing messages. * [WhatsApp Webhook Override (Error 100)](https://dualhook.com/docs/whatsapp-error-100-webhook-override) * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-130403#faq) ----------------------------------------------------------- **Q: Who blocked whom in error 130403?** A: **Your business** blocked the end user. (If the _user_ blocked you, delivery fails differently, often surfacing as undeliverable / 131026.) **Q: Should I retry?** A: No. Retrying won't deliver. Unblock the user first. **Q: How do I unblock?** A: Use Meta's Block API "Unblock a user" endpoint (or WhatsApp Manager), then resend. **Q: Does 130403 mean Meta banned my account?** A: No — that would be 368 or 131031. 130403 is specifically your business's block on one recipient. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-130403#how-dualhook-helps) ----------------------------------------------------------------------------------------- Error 130403 is a **send-side** condition tied to your business's block list. Dualhook relays message sends without content persistence but cannot unblock a user for you. Use the supported Meta or WhatsApp Manager control for your business. Delivery-failure status webhooks continue to route directly to your endpoint. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Compliance & Data Retention | Dualhook Browse Docs Compliance & Data Retention =========================== Privacy boundary, configurable retention, CSV export, and organization isolation. Looking for the high-level overview first? Start with [WhatsApp API Privacy](https://dualhook.com/whatsapp-api-privacy) and then return here for the implementation details. Privacy Boundary[](https://dualhook.com/docs/compliance#privacy-boundary) -------------------------------------------------------------------------- Dualhook enforces a strict metadata-only boundary for operations: * **No message body storage** — message content routes directly from Meta to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) . * **No inbound content proxy** — inbound message-path webhooks and media references go directly from Meta to your endpoint through Webhook Override. * **No outbound content retention** — outbound message payloads and requested media transit the allowlisted Dualhook runtime without persistence, caching, or body logging. * **No conversation archive** — Dualhook does not maintain a record of message threads. Only operational metadata needed for setup, monitoring, and compliance features is stored. This includes webhook delivery logs, [health check](https://dualhook.com/docs/health-monitoring) snapshots, [template](https://dualhook.com/docs/templates) management records, and connection configuration. Configurable Retention[](https://dualhook.com/docs/compliance#configurable-retention) -------------------------------------------------------------------------------------- Organization admins can set retention windows for webhook logs and health checks: | Window | Use case | | --- | --- | | 7 days | Minimal retention for lean operations | | 30 days | Standard operational window | | 90 days | Extended retention for audit and compliance | Records older than the configured retention policy are purged automatically. Retention settings apply per organization and can be adjusted at any time from the Dualhook dashboard. CSV Export[](https://dualhook.com/docs/compliance#csv-export) -------------------------------------------------------------- Dualhook supports CSV export of webhook logs for audits and incident analysis. Current CSV columns: | Column | Description | | --- | --- | | `id` | Internal log record ID | | `event_type` | Webhook event type | | `delivery_status` | Delivery outcome (`delivered`, `failed`, `pending`) | | `waba_id` | WhatsApp Business Account ID | | `phone_number_id` | Phone number ID | | `trace_id` | Dualhook trace identifier | | `received_at` | Timestamp when event was received | | `delivered_at` | Timestamp when event was forwarded | | `error_message` | Error details (if applicable) | Organization Isolation[](https://dualhook.com/docs/compliance#organization-isolation) -------------------------------------------------------------------------------------- All compliance settings and exported data are organization-scoped. One organization cannot access another organization's logs, settings, or connection data. This isolation extends to all Dualhook features: webhook logs, health checks, template management, and connection configuration are fully partitioned by organization. The same organization boundary also applies to in-app support conversations. Support tickets are attached to the workspace that created them, which helps keep operational context and access controls aligned. For architecture and security details, see [Architecture & Security](https://dualhook.com/docs/architecture-security) . For formal legal terms, see the [Privacy Policy](https://dualhook.com/privacy) , [DPA](https://dualhook.com/dpa) , [Subprocessors](https://dualhook.com/subprocessors) , and [Security / TOMs](https://dualhook.com/security) . For account health management, see [Maintain Account Health](https://dualhook.com/docs/account-health) . For the support workflow itself, see [Support, Contact, and Bug Reports](https://dualhook.com/docs/contact-support) . Related ------- * [WhatsApp API Architecture & Security](https://dualhook.com/docs/architecture-security) Cloud API architecture, encryption, data residency, GDPR, and Dualhook's direct-routing security model. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [About Dualhook](https://dualhook.com/docs/about) What Dualhook is, where it fits in the WhatsApp ecosystem, and how it works. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # MM API Onboarding | Dualhook Browse Docs MM API Onboarding ================= Reference for MM API onboarding outside Dualhook's managed configuration. Dualhook does not request Marketing Messages Lite. Overview[](https://dualhook.com/docs/marketing-messages-onboarding#overview) ----------------------------------------------------------------------------- > **Outside Dualhook's managed flow:** Dualhook's custom Embedded Signup v4 configuration intentionally selects WhatsApp assets only and does not request Marketing Messages Lite, ad assets, `ads_read`, or the `marketing_messages_lite` feature. A `dh_live_...` key cannot perform the operations on this page. Businesses that need MM API must use a separately reviewed, customer-owned Meta app and authorization. Onboarding to **Marketing Messages API for WhatsApp** is a one-time per-WABA action: the customer's business signs Meta's MM API Terms of Service. Once signed, the WABA can send marketing templates through `/marketing_messages` — see [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) for the send mechanics. Onboarding is non-destructive: the existing Cloud API setup keeps working, the same business phone numbers and templates remain in place, and you can keep sending marketing through Cloud API alongside MM API until you choose to disable that path. Prerequisites[](https://dualhook.com/docs/marketing-messages-onboarding#prerequisites) --------------------------------------------------------------------------------------- To be eligible for MM API onboarding, the WABA must: * Be active and not restricted from messaging due to policy violations. * Have a tax country and an owner business country that are **not** in sanctioned regions (Cuba, Iran, North Korea, Syria, Crimea, Donetsk, Luhansk). * Have at least one phone number registered on Cloud API. MM API does not register new numbers — it reuses the Cloud API registration. Numbers not registered on Cloud API cannot be used for MM API. App-side, the partner app needs **advanced access** for: * `whatsapp_business_messaging` — to call `/marketing_messages` * `whatsapp_business_management` — to manage WABAs, phone numbers, and templates * `ads_read` (optional) — required only to call the Insights API for conversion metrics Check Eligibility[](https://dualhook.com/docs/marketing-messages-onboarding#check-eligibility) ----------------------------------------------------------------------------------------------- The canonical eligibility check is the `marketing_messages_onboarding_status` field on the WABA: curl 'https://graph.facebook.com//?fields=marketing_messages_onboarding_status' \ -H 'Authorization: Bearer ' { "id": "102290129340398", "marketing_messages_onboarding_status": "ELIGIBLE" } Possible values include `ELIGIBLE`, `ONBOARDED`, and others — see Meta's WhatsApp Business Account API reference for the full list. For partners discovering eligible WABAs across many customers in one call, use the Client WABAs endpoint with a filter: curl -g 'https://graph.facebook.com///client_whatsapp_business_accounts?filtering=[{"field":"marketing_messages_onboarding_status","operator":"IN","value":["ELIGIBLE"]}]' \ -H 'Authorization: Bearer ' A legacy field `marketing_messages_lite_api_status` exists for the same purpose but is being deprecated in version 24.0. Use `marketing_messages_onboarding_status` for new code. Customer-owned onboarding only[](https://dualhook.com/docs/marketing-messages-onboarding#customer-owned-onboarding-only) ------------------------------------------------------------------------------------------------------------------------- Dualhook does not onboard, authorize, or proxy Marketing Messages API for WhatsApp. If a business needs this separate Meta product, it must use its own reviewed Meta app, credentials, and authorization flow. The following Meta-managed options may be available to an eligible business: | Path | Who uses it | How | | --- | --- | --- | | **WhatsApp Manager (recommended for direct customers and partner-managed flows)** | Business admins with full control of the portfolio | Open WhatsApp Manager → Overview → Alerts section → "Accept terms to get started for Marketing Messages API for WhatsApp" | | **App Dashboard (direct integrators only)** | Solo developers and businesses without a partner | App Dashboard → WhatsApp → Quickstart → "Improve ROI with Marketing Messages API for WhatsApp" → "Get started" → "Continue to integration guide" | Dualhook's Embedded Signup configuration must not be modified to include MM Lite, ad permissions, or ad assets. A customer-owned integration must keep its credentials entirely separate from Dualhook. ToS Signed Webhook[](https://dualhook.com/docs/marketing-messages-onboarding#tos-signed-webhook) ------------------------------------------------------------------------------------------------- The preferred onboarding-completion signal is an `account_update` webhook with event `MM_LITE_TERMS_SIGNED`, fired once the customer's business signs the MM API ToS. Available since September 8, 2025. { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "time": "",\ "changes": [\ {\ "field": "account_update",\ "value": {\ "event": "MM_LITE_TERMS_SIGNED",\ "waba_info": {\ "owner_business_id": "",\ "waba_id": "102290129340398"\ }\ }\ }\ ]\ }\ ] } When you see this webhook for a given WABA, it is safe to start sending through `/marketing_messages` for that WABA. The webhook fires once per WABA under the portfolio, not once per portfolio. Legacy: AD\_ACCOUNT\_LINKED Webhook[](https://dualhook.com/docs/marketing-messages-onboarding#legacy-ad_account_linked-webhook) -------------------------------------------------------------------------------------------------------------------------------- Before the ToS-signed webhook existed, MM API onboarding was tracked via an `account_update` webhook with event `AD_ACCOUNT_LINKED`. This webhook still fires but is now considered legacy — Meta no longer grants partners access to the linked ad account, so the `ad_account_id` it carries is read-only background metadata. { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "time": "",\ "changes": [\ {\ "field": "account_update",\ "value": {\ "event": "AD_ACCOUNT_LINKED",\ "waba_info": {\ "waba_id": "102290129340398",\ "ad_account_id": "",\ "owner_business_id": ""\ }\ }\ }\ ]\ }\ ] } For new code, treat `MM_LITE_TERMS_SIGNED` as the source of truth and use `AD_ACCOUNT_LINKED` only as a fallback for legacy reporting. What Onboarding Creates[](https://dualhook.com/docs/marketing-messages-onboarding#what-onboarding-creates) ----------------------------------------------------------------------------------------------------------- When a business onboards to MM API, Meta creates **read-only ad accounts** under their business portfolio, one per WABA, synced to the WABA's marketing templates. These ad accounts exist purely as a technical construct so: * Marketing templates and their performance show up in Ads Manager's "Marketing Messages" tab * The Insights API can return per-template metrics including conversion events * Conversions reported via Pixel/Conversions API/Meta SDK can be attributed back to the originating marketing message Marketing teams are **not** Ads teams — the "Ads" terminology here is technical scaffolding only. The business does not run ads through these accounts and partners do not get access to spend, billing, or campaign editing. Template-to-ad sync takes up to 10 minutes per new template. Templates that go unused for more than 7 days require another 10-minute sync after the first reuse. Wait that long after creating a new marketing template before sending production traffic with it — sending too soon can produce errors or skip optimization/measurement. Event Activity Sharing[](https://dualhook.com/docs/marketing-messages-onboarding#event-activity-sharing) --------------------------------------------------------------------------------------------------------- Once onboarded, message status events (delivered, read, clicked) are automatically shared with Meta to optimize delivery. Meta does not sell this data; it is used solely for marketing-message optimization. To disable globally for a WABA, toggle "Share event activity" off in WhatsApp Business Account settings. To override per send, include `"message_activity_sharing": false` (or `true`) on the `/marketing_messages` request body. The per-message value overrides the WABA-level default for that single send only. Related ------- * [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) Reference for Meta's separate /marketing\_messages product, which is not exposed by Dualhook's runtime or Embedded Signup configuration. * [Marketing Messages](https://dualhook.com/docs/marketing-messages) Marketing template messaging through Dualhook's standard Cloud API runtime, plus boundaries for Meta's separate MM API product. * [Embedded Signup](https://dualhook.com/docs/embedded-signup) How Dualhook uses Meta's Embedded Signup flow to connect WhatsApp Business Accounts via OAuth. * [WhatsApp Business API Permissions](https://dualhook.com/docs/whatsapp-business-api-permissions) OAuth scopes, WABA task access, delegated Meta access, and troubleshooting missing WhatsApp Business API permissions. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Marketing Messages: Cloud API vs Marketing Messages API | Dualhook Browse Docs Marketing Messages ================== Marketing template messaging through Dualhook's standard Cloud API runtime, plus boundaries for Meta's separate MM API product. Marketing templates are the one category with two send paths: the standard Cloud API `/messages` endpoint, which Dualhook's runtime supports, and Meta's separate Marketing Messages API, which it does not. This page explains where the line sits, how per-user marketing limits and opt-out affect delivery, and what is billed when, so you can choose a path before building campaigns. What Counts as Marketing[](https://dualhook.com/docs/marketing-messages#what-counts-as-marketing) -------------------------------------------------------------------------------------------------- A marketing message is a [template](https://dualhook.com/docs/templates) message intended to promote, re-engage, or drive new user action. Examples include product or feature announcements, promotional campaigns, and seasonal reminders or win-back campaigns. If content mixes utility and promotional intent, Meta will reclassify it as marketing — see [Template Category Reclassification](https://dualhook.com/docs/webhook-events#template-category-reclassification) . Two Send Paths: Cloud API vs MM API[](https://dualhook.com/docs/marketing-messages#two-send-paths-cloud-api-vs-mm-api) ----------------------------------------------------------------------------------------------------------------------- Marketing templates are the only template category that can be sent through **two** different endpoints: | Send path | Endpoint | When to use | | --- | --- | --- | | Cloud API (default) | `POST //messages` | All accounts, all marketing templates. Standard delivery, no creative or delivery optimizations. | | Marketing Messages API for WhatsApp (MM API) | `POST //marketing_messages` | Eligible WABAs that have signed the MM API ToS. Adds quality-based delivery optimization, creative optimizations, conversion measurement, longer TTL, and a max-price beta. | Dualhook supports marketing templates through the standard Cloud API `/messages` runtime route. Dualhook's Embedded Signup configuration does not request Marketing Messages Lite, and `dh_live_...` keys cannot call `/marketing_messages`. The MM API references in this section are informational for businesses using a separately reviewed, customer-owned Meta app and authorization. Marketing Pages in This Section[](https://dualhook.com/docs/marketing-messages#marketing-pages-in-this-section) ---------------------------------------------------------------------------------------------------------------- * [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) — endpoint, features vs Cloud API, status webhooks, fallback behavior * [MM API Onboarding](https://dualhook.com/docs/marketing-messages-onboarding) — explains why MM API onboarding is outside Dualhook's managed configuration * [Conversion Tracking](https://dualhook.com/docs/marketing-conversion-tracking) — Meta Pixel, Conversions API, Meta SDK, click-event webhooks, deep-link attribution * [Creative Optimizations](https://dualhook.com/docs/marketing-creative-optimizations) — `creative_features_spec`, template-level vs WABA-level opt-in/opt-out * [Marketing Deep Links](https://dualhook.com/docs/marketing-deep-links) — Android `app_deep_link` on URL buttons, fallback URLs, attribution preservation * [Max-Price (Beta)](https://dualhook.com/docs/marketing-max-price) — `bid_spec`, per-message multiplier, reach estimation * Specialty templates (Coupon code, Limited-time offer, Media carousel, Call permission request, etc.) — see [Specialty Marketing Templates](https://dualhook.com/docs/marketing-messages#specialty-marketing-templates) below Prerequisites[](https://dualhook.com/docs/marketing-messages#prerequisites) ---------------------------------------------------------------------------- Before sending any marketing template: * An approved template with category `MARKETING`. * Documented user opt-in for marketing communication (see [Compliance](https://dualhook.com/docs/compliance) ). * Sending from a healthy phone number (`AVAILABLE` — see [Health Monitoring](https://dualhook.com/docs/health-monitoring) ). For MM API specifically, a separate customer-owned Meta integration is required. It is not enabled by Dualhook Embedded Signup. Specialty Marketing Templates[](https://dualhook.com/docs/marketing-messages#specialty-marketing-templates) ------------------------------------------------------------------------------------------------------------ Beyond the standard custom marketing template (header + body + footer + buttons), Meta supports several specialty marketing template types. All are created through the same `//message_templates` endpoint by varying the `components` array. | Type | What it does | | --- | --- | | **Custom marketing** | Standard structure: optional header, required body, optional footer, up to 10 buttons | | **Coupon code** | Single `copy_code` button copies an offer code to the recipient's clipboard on tap (max 20 chars) | | **Limited-time offer** | `limited_time_offer` component with optional countdown timer to a Unix-millisecond expiration. WhatsApp web/desktop users see a "not supported" placeholder | | **Media card carousel** | `carousel` component with up to 10 media cards (image or video header per card, optional body, up to 2 buttons per card). All cards must share the same component shape | | **Product card carousel** | Same shape as media carousel but cards reference catalog products (keeps the user inside WhatsApp instead of opening the browser) | | **Multi-product** | Up to 30 products from your catalog organized into up to 10 sections, in one message | | **Single-product** | One product card with shop-in-WhatsApp behavior | | **Catalog** | Full product catalog browsable inside WhatsApp | | **Call permission request** | Required body + `call_permission_request` component; recipient grants/denies permission for your business to call them. Cannot be combined with other interactive components | Body text is required and must not be empty. Footer components are not supported on limited-time offer templates. Specialty components frequently exclude or require other components — refer to Meta's [marketing template documentation](https://developers.facebook.com/docs/whatsapp/business-management-api/message-templates/marketing-templates) for the exact component matrix per type before building. User Preferences and Opt-Out[](https://dualhook.com/docs/marketing-messages#user-preferences-and-opt-out) ---------------------------------------------------------------------------------------------------------- WhatsApp gives recipients a per-business "Offers and announcements" setting with three actions: | User action | Webhook? | Effect on sends | | --- | --- | --- | | Mark "Interested" / "Not interested" | No | Affects per-user marketing limits between you and that user | | Stop marketing messages | Yes (`user_preferences`) | API accepts the request but does not deliver. Status webhook returns `failed` with code `131050` | | Resume marketing messages | Yes (`user_preferences`) | Delivery resumes | Subscribe to the `user_preferences` webhook field to track stop/resume actions. Suppress users on `failed` + `131050` to avoid wasting send budget on undeliverable recipients. Per-User Marketing Limits[](https://dualhook.com/docs/marketing-messages#per-user-marketing-limits) ---------------------------------------------------------------------------------------------------- WhatsApp dynamically caps how many marketing template messages any one user receives across all businesses, weighted by that user's individual marketing-message read rate. You cannot inspect or override this limit; it is enforced by Meta. Per-user limits are not active in the EEA, UK, Japan, and South Korea — but per-recipient quality signals still apply globally. The simplest mitigations: keep templates relevant, send only to users who actively opted in, and don't reuse the same template too frequently. Billing and Windows[](https://dualhook.com/docs/marketing-messages#billing-and-windows) ---------------------------------------------------------------------------------------- * Marketing template messages are billed by Meta under current rate-card rules — see [Pricing](https://dualhook.com/docs/pricing) . * Inside the 72-hour free entry point window, marketing messages are free. * Marketing templates do **not** have volume tiers — only utility and authentication do. * Both `/messages` and `/marketing_messages` use the same per-message billing model. The status webhook `pricing.category` distinguishes them: `marketing` for Cloud API sends, `marketing_lite` for MM API sends. Dualhook does not charge per message and does not change Meta billing classification. Content and Compliance[](https://dualhook.com/docs/marketing-messages#content-and-compliance) ---------------------------------------------------------------------------------------------- * Keep claims accurate and avoid misleading urgency. * Do not send prohibited content categories. * Use clear, user-friendly language with explicit brand identity. * Respect opt-out handling and suppress users who revoked consent. Note: marketing messages to recipients in the **United States** are blocked by Meta with error `131049` since April 1, 2025 — this is platform-wide and not specific to MM API. Quality and Deliverability[](https://dualhook.com/docs/marketing-messages#quality-and-deliverability) ------------------------------------------------------------------------------------------------------ Marketing sends affect [quality rating](https://dualhook.com/docs/capacity-quality-limits) more quickly than transactional traffic. If quality degrades: * Slow down campaign volume. * Review audience targeting and opt-in quality. * Reduce repetitive or low-value promotional content. Persistent negative feedback can reduce messaging limits or lead to restrictions by Meta. Related ------- * [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) Reference for Meta's separate /marketing\_messages product, which is not exposed by Dualhook's runtime or Embedded Signup configuration. * [MM API Onboarding](https://dualhook.com/docs/marketing-messages-onboarding) Reference for MM API onboarding outside Dualhook's managed configuration. Dualhook does not request Marketing Messages Lite. * [Marketing Conversion Tracking](https://dualhook.com/docs/marketing-conversion-tracking) Measure conversions from marketing messages with Meta Pixel, Conversions API, and the Meta SDK. Includes fbclid handling, the click-event webhook, and Android deep-link attribution. * [Marketing Creative Optimizations](https://dualhook.com/docs/marketing-creative-optimizations) Configure Meta's automatic creative optimizations on MM API marketing templates: creative\_features\_spec at template and WABA level, opt-in/opt-out per key. * [Marketing Deep Links](https://dualhook.com/docs/marketing-deep-links) Map a URL button on a marketing template to an Android app deep link via app\_deep\_link, with fallback URLs for non-Android clients and uninstalled-app users. * [Marketing Max-Price (Beta)](https://dualhook.com/docs/marketing-max-price) Set a maximum price per marketing message delivery on MM API for WhatsApp using bid\_spec. Includes per-message multiplier, reach estimation, and rollout phases. * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. * [Utility Messages](https://dualhook.com/docs/utility-messages) Utility template messaging hub: components and buttons, specialty types, the October 2026 CSW billing change, volume tiers, content rules, and recategorization. * [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) API rate limits, messaging limit tiers, quality rating, and scaling guidance. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp account_update Webhook: WABA Lifecycle Events Reference | Dualhook Browse Docs account\_update Webhook ======================= Field reference for WhatsApp account\_update webhooks: WABA lifecycle events, restrictions, partner events, pricing tiers, MM API terms, offboarding, and Dualhook handling. The `account_update` field is the broad WABA lifecycle webhook. It covers partner sharing, permission grants and revocations, policy violations, offboarding, reconnection, pricing-tier changes, MM API terms, and business-location updates. What account\_update Reports[](https://dualhook.com/docs/webhook-account-update#what-account_update-reports) ------------------------------------------------------------------------------------------------------------- Use this field for account-level changes that affect whether a WABA can stay connected, send messages, use specific features, or remain shared with a provider. It can report: * WABA deletion, restriction, violation, disablement, or reinstatement, * partner app install, uninstall, added, or removed events, * device-change or phone-number-reregistration offboarding and reconnection, * authentication-international rate eligibility, * primary business location country changes, * MM API for WhatsApp terms acceptance, * volume-based pricing tier changes, * partner-led business verification status changes. Key Event Values[](https://dualhook.com/docs/webhook-account-update#key-event-values) -------------------------------------------------------------------------------------- | Event | What to do | | --- | --- | | `ACCOUNT_DELETED` | Treat the WABA as no longer usable. | | `ACCOUNT_RESTRICTION` | Read `restriction_info` and stop affected actions until resolved or expired. | | `ACCOUNT_VIOLATION` / `DISABLED_UPDATE` | Escalate account quality and policy review. | | `PARTNER_APP_INSTALLED` | A customer granted app permissions. Confirm expected WABA ownership and scopes. | | `PARTNER_APP_UNINSTALLED` | A customer revoked app permissions. Expect token or management failures. | | `PARTNER_REMOVED` | The WABA was unshared from the partner. If `disconnection_info` is present, inspect the reason. | | `ACCOUNT_OFFBOARDED` | The WABA was offboarded after device change or phone number reregistration. | | `ACCOUNT_RECONNECTED` | The WABA reconnected after a device change or phone number reregistration. | | `AUTH_INTL_PRICE_ELIGIBILITY_UPDATE` | Store the effective start time and exception countries. | | `VOLUME_BASED_PRICING_TIER_UPDATE` | Update pricing-tier reporting for the category, region, and effective month. | | `MM_LITE_TERMS_SIGNED` | The WABA accepted MM API for WhatsApp terms. | Payload Shape[](https://dualhook.com/docs/webhook-account-update#payload-shape) -------------------------------------------------------------------------------- The wrapper is stable; the nested object changes by `event`. { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "time": 1743451903,\ "changes": [\ {\ "field": "account_update",\ "value": {\ "event": "ACCOUNT_RESTRICTION",\ "restriction_info": [\ {\ "restriction_type": "RESTRICTED_BIZ_INITIATED_MESSAGING",\ "expiration": 1743451903\ }\ ]\ }\ }\ ]\ }\ ] } Useful nested objects: | Object | Used for | | --- | --- | | `waba_info` | Partner app events, partner sharing, MM API terms, ad account linking. | | `violation_info` | Policy or terms violations. | | `restriction_info` | Account restrictions and any remediation text. | | `ban_info` | Disablement and reinstatement state. | | `disconnection_info` | Coexistence disconnection reason and who initiated it. | | `auth_international_rate_eligibility` | Authentication-international pricing effective dates. | | `volume_tier_info` | Volume-based pricing tier, category, region, and effective month. | Dualhook Handling[](https://dualhook.com/docs/webhook-account-update#dualhook-handling) ---------------------------------------------------------------------------------------- Dualhook treats `account_update` as a high-priority management field. It is received on Dualhook's app-level callback, interpreted for connection and account state, and forwarded to your configured endpoint. Dualhook uses these events to surface account restrictions, offboarding, reconnection, partner removal, permission revocation, pricing-tier signals, and MM API terms acceptance. Dualhook stores operational metadata and delivery status only; no customer message bodies or media are part of this field. The routing effect depends on the event: * `PARTNER_REMOVED`, `PARTNER_APP_UNINSTALLED`, and `ACCOUNT_DELETED` terminally remove only connection rows on the affected customer WABA. Other WABAs in the same organization are untouched. * A terminal partner event is applied only when it includes the affected WABA and a valid `entry.time`, and only when that event is not older than the connection's current authorization. Malformed or stale deliveries are retained for investigation but do not delete anything. * `ACCOUNT_OFFBOARDED` places a reversible hold. `ACCOUNT_RECONNECTED` releases it only after Meta access and webhook routing are validated. * A disablement or scheduled disablement places a durable account hold. Reinstatement releases it only after validation. * Business-initiated and customer-initiated messaging restrictions block the connection until Meta's supplied expiry. Calling, phone-add, and template-only restrictions remain alerts for the affected operation rather than full connection blocks. * Partner-added and app-installed events are onboarding evidence only. They never reactivate an existing connection by themselves. Implementation Notes[](https://dualhook.com/docs/webhook-account-update#implementation-notes) ---------------------------------------------------------------------------------------------- * Switch on `value.event` first, then parse the nested object for that event. * Keep unknown event values. Meta adds new account events over time. * Treat partner removal/deletion as WABA-scoped terminal events and offboarding/reconnection as reversible routing events. * `PARTNER_REMOVED` can include `disconnection_info` for WhatsApp Business app plus Cloud API setups. Inspect the reason before choosing a recovery path. Documented `disconnection_info.reason` values: | Reason | Meaning | | --- | --- | | `ACCOUNT_DISCONNECTED` | The account was disconnected due to enforcement, or the client explicitly deleted their WhatsApp account. | | `BUSINESS_DOWNGRADE` | The client registered the business phone number with the consumer WhatsApp app. | | `CHANGE_NUMBER` | The client changed their phone number. | | `COMPANION_INACTIVITY` | A companion device was inactive for approximately 30 days. | | `PRIMARY_INACTIVITY` | The primary device was inactive for approximately 14 days. | | `USER_RE_REGISTERED` | The client re-registered on a new device. | * Multiple `VOLUME_BASED_PRICING_TIER_UPDATE` events can describe the same tier change. Use the earliest `tier_update_time` for canonical reporting. Related[](https://dualhook.com/docs/webhook-account-update#related) -------------------------------------------------------------------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) * [Maintain Account Health](https://dualhook.com/docs/account-health) * [Pricing](https://dualhook.com/docs/pricing) * [MM API Onboarding](https://dualhook.com/docs/marketing-messages-onboarding) Related ------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting. * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) Management webhook events: template status, quality updates, account alerts, and forwarding. * [Maintain Account Health](https://dualhook.com/docs/account-health) Ownership hygiene, quality monitoring, opt-out governance, and incident playbook. * [Pricing](https://dualhook.com/docs/pricing) Dualhook subscription plans, what is included, and how pricing relates to Meta messaging fees. * [MM API Onboarding](https://dualhook.com/docs/marketing-messages-onboarding) Reference for MM API onboarding outside Dualhook's managed configuration. Dualhook does not request Marketing Messages Lite. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Location Templates | Dualhook Browse Docs Location Templates ================== WhatsApp templates with a map header. Specify latitude, longitude, name, and address at send time. Useful for delivery updates, store finders, ride-hailing pickup and drop-off. What Location Templates Do[](https://dualhook.com/docs/utility-templates-location#what-location-templates-do) -------------------------------------------------------------------------------------------------------------- Location templates include a map header that displays a specific location. When a WhatsApp user taps the map, their default map app opens to the specified coordinates. The location is **specified at send time**, not at template creation time. This means a single approved template can be reused for many different destinations — order tracking, delivery updates, ride-hailing pickup and drop-off, store finders. Location templates can be categorized as either `UTILITY` or `MARKETING`. Limitations[](https://dualhook.com/docs/utility-templates-location#limitations) -------------------------------------------------------------------------------- * Only `UTILITY` or `MARKETING` templates can include a location header. * **Real-time locations are not supported.** The location is static — the map shows whatever lat/lng you sent at send time. Updating it requires sending a new message. * The location (latitude, longitude, name, address) is specified at send time, not at template creation time. Supported Components[](https://dualhook.com/docs/utility-templates-location#supported-components) -------------------------------------------------------------------------------------------------- | Component | Cardinality | Notes | | --- | --- | --- | | Location header | 1 (required) | `format: location` — no value at creation time | | Body | 1 (required) | Up to 1024 characters. Supports named parameters | | Footer | 0 or 1 | Up to 60 characters | | Buttons | Optional | Standard button types | Create a Location Template[](https://dualhook.com/docs/utility-templates-location#create-a-location-template) -------------------------------------------------------------------------------------------------------------- curl 'https://graph.facebook.com///message_templates' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "order_delivery_update", "language": "en_US", "category": "UTILITY", "parameter_format": "named", "components": [\ { "type": "header", "format": "location" },\ {\ "type": "body",\ "text": "Good news {{customer_name}}! Your order #{{order_number}} is on its way to the location above.",\ "example": {\ "body_text_named_params": [\ { "param_name": "customer_name", "example": "Mark" },\ { "param_name": "order_number", "example": "566701" }\ ]\ }\ },\ { "type": "footer", "text": "To stop receiving delivery updates, tap the button below." },\ {\ "type": "buttons",\ "buttons": [\ { "type": "quick_reply", "text": "Stop Delivery Updates" }\ ]\ }\ ] }' The header at creation time is just `{ "type": "header", "format": "location" }` — no coordinates yet. Coordinates are bound at send time. Example response: { "id": "123456789012345", "status": "PENDING", "category": "UTILITY" } Send a Location Template[](https://dualhook.com/docs/utility-templates-location#send-a-location-template) ---------------------------------------------------------------------------------------------------------- The location is supplied in the header `parameters` array on the send call: curl 'https://api.dualhook.com/v25.0//messages' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "messaging_product": "whatsapp", "recipient_type": "individual", "to": "12015550123", "type": "template", "template": { "name": "order_delivery_update", "language": { "policy": "deterministic", "code": "en_US" }, "components": [\ {\ "type": "header",\ "parameters": [\ {\ "type": "location",\ "location": {\ "latitude": "37.44211676562361",\ "longitude": "-122.16155960083124",\ "name": "Philz Coffee",\ "address": "101 Forest Ave, Palo Alto, CA 94301"\ }\ }\ ]\ },\ {\ "type": "body",\ "parameters": [\ { "type": "text", "parameter_name": "customer_name", "text": "Jane" },\ { "type": "text", "parameter_name": "order_number", "text": "892104" }\ ]\ }\ ] } }' The send-time location values are independent of the template's creation-time configuration — only the body parameter values need to match the template's named parameter slots. Field Reference[](https://dualhook.com/docs/utility-templates-location#field-reference) ---------------------------------------------------------------------------------------- | Field | Required? | What it is | | --- | --- | --- | | `latitude` | Yes | Decimal degrees as a string (e.g. `"37.44211676562361"`) | | `longitude` | Yes | Decimal degrees as a string (e.g. `"-122.16155960083124"`) | | `name` | Optional | Display name for the location (e.g. `"Philz Coffee"`) | | `address` | Optional | Display address (e.g. `"101 Forest Ave, Palo Alto, CA 94301"`) | `name` and `address` are not validated against the coordinates — Meta displays whatever you send. Make sure your downstream system uses canonical addresses if the user will rely on them for navigation. Use Cases[](https://dualhook.com/docs/utility-templates-location#use-cases) ---------------------------------------------------------------------------- * Delivery in transit — show the courier's current waypoint or the destination address. * Store finder — confirm which physical location the user's order is being prepared at. * Ride-hailing — communicate pickup or drop-off coordinates. * Service appointment — show where the technician will arrive or where the customer should bring an item. * Event check-in — direct attendees to a venue entrance. For ongoing live tracking, send periodic location-template messages — but don't send so frequently that you trigger per-user rate limits or hurt your [quality rating](https://dualhook.com/docs/capacity-quality-limits) . Related ------- * [Utility Messages](https://dualhook.com/docs/utility-messages) Utility template messaging hub: components and buttons, specialty types, the October 2026 CSW billing change, volume tiers, content rules, and recategorization. * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. * [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) How to send template messages via Cloud API with variables, media headers, and URL buttons. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp history Webhook: Coexistence Chat History Sync | Dualhook Browse Docs history Webhook =============== Field reference for WhatsApp history webhooks: Coexistence chat-history sync, phases, chunks, declined sharing, direct routing, and Dualhook handling. Looking for the high-level overview first? Start with [WhatsApp Coexistence](https://dualhook.com/whatsapp-coexistence) and then return here for the implementation details. The `history` field is the Coexistence chat-history sync field. It is message-path data and can contain prior WhatsApp Business app conversation content. In Dualhook setups, it is routed directly from Meta to your endpoint via Webhook Override. What history Reports[](https://dualhook.com/docs/webhook-history#what-history-reports) --------------------------------------------------------------------------------------- When a business customer has onboarded a WhatsApp Business app number to Cloud API and agrees to share history, Meta can send prior chat history for up to 180 days before onboarding. Important constraints: * group chats are not included, * media messages usually appear first as `media_placeholder`, * separate media-detail webhooks can follow for media sent within roughly 14 days of onboarding, * a single webhook can contain many threads and many messages. When It Fires[](https://dualhook.com/docs/webhook-history#when-it-fires) ------------------------------------------------------------------------- The field fires after a solution provider requests history sync for a business that was onboarded with a WhatsApp Business app phone number. There are two outcomes: | Outcome | Payload | | --- | --- | | Business approved history sharing | One or more `history` chunks containing thread and message data. | | Business declined history sharing | A `history.errors[]` payload, commonly code `2593109`. | How History Sync Is Delivered[](https://dualhook.com/docs/webhook-history#how-history-sync-is-delivered) --------------------------------------------------------------------------------------------------------- History sync is split into phases: | Phase | Coverage | | --- | --- | | `0` | onboarding time through day 1 | | `1` | day 1 through day 90 | | `2` | day 90 through day 180 | Within a phase, Meta may send multiple chunks. Use `chunk_order` to order chunks and `progress` to track overall sync progress. A `progress` value of `100` means the sync is complete. Payload Shape[](https://dualhook.com/docs/webhook-history#payload-shape) ------------------------------------------------------------------------- { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "changes": [\ {\ "field": "history",\ "value": {\ "messaging_product": "whatsapp",\ "metadata": {\ "display_phone_number": "",\ "phone_number_id": ""\ },\ "history": [\ {\ "metadata": {\ "phase": 0,\ "chunk_order": 1,\ "progress": 55\ },\ "threads": [\ {\ "id": "",\ "messages": [\ {\ "from": "",\ "id": "",\ "timestamp": "1739230955",\ "type": "text",\ "text": {\ "body": "Example prior message"\ },\ "history_context": {\ "status": "READ"\ }\ }\ ]\ }\ ]\ }\ ]\ }\ }\ ]\ }\ ] } Declined Sharing[](https://dualhook.com/docs/webhook-history#declined-sharing) ------------------------------------------------------------------------------- If the business declined history sharing, Meta can send an error payload instead of threads. { "messaging_product": "whatsapp", "metadata": { "display_phone_number": "", "phone_number_id": "" }, "history": [\ {\ "errors": [\ {\ "code": 2593109,\ "title": "History sync is turned off by the business from the WhatsApp Business App"\ }\ ]\ }\ ] } Live message delivery can still work when history sharing is declined. The error only means pre-onboarding history will not be shared. Dualhook Handling[](https://dualhook.com/docs/webhook-history#dualhook-handling) --------------------------------------------------------------------------------- Dualhook does not ingest, store, forward, or replay `history` payloads. This field is direct-routed through Webhook Override, so Meta sends it to your configured endpoint. During Coexistence onboarding, Dualhook triggers Meta's one-time contact sync first and history sync second after Webhook Override is accepted. If Meta rejects contact sync, Dualhook still attempts history because these are separate one-time requests and both must be initiated within 24 hours. Keep WhatsApp Business open while Meta prepares the sync. A successful history trigger means Meta accepted the request; it does not prove that the business shared history, that chunks were generated, or that your endpoint received every chunk. If your endpoint misses or drops history chunks, Dualhook has no copy to replay. Fix your webhook handler first, then re-onboard only if you need Meta to attempt a fresh sync and the platform allows it. Implementation Notes[](https://dualhook.com/docs/webhook-history#implementation-notes) --------------------------------------------------------------------------------------- * Acknowledge quickly and process asynchronously. One webhook can contain thousands of messages. * Use `phase`, `chunk_order`, and `progress` for ordering and progress tracking. * Treat `media_placeholder` as a pointer that may be followed by a separate media-detail webhook. * Make ingestion idempotent by `wamid`, phase, and chunk metadata. * If `wa_id` fields change during Meta's BSUID transition, handle `user_id` and missing phone-number fields as described in the BSUID guide. Related[](https://dualhook.com/docs/webhook-history#related) ------------------------------------------------------------- * [Webhook Override](https://dualhook.com/docs/webhook-override) * [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) * [BSUID Transition Guide](https://dualhook.com/docs/contacts-bsuid) Related ------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) How Coexistence mode works: using WhatsApp Business App and Cloud API on the same number. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. * [Meta BSUID, Usernames & Contact Book Transition Guide](https://dualhook.com/docs/contacts-bsuid) Overview of Meta's WhatsApp BSUID rollout: usernames, contact book behavior, parent BSUIDs, API recipient support, webhook changes, and migration timeline. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Parent BSUIDs for Multi-Portfolio Businesses | Dualhook Browse Docs Parent BSUIDs ============= Parent BSUID behavior for eligible multi-portfolio WhatsApp businesses, including payload fields, account lookup, supported APIs, and limitations. Parent BSUIDs are an optional identity layer for managed businesses with multiple business portfolios. They let enrolled portfolios use one parent-scoped identifier for the same WhatsApp user. What Parent BSUIDs Are[](https://dualhook.com/docs/contacts-bsuid-parent-user-ids#what-parent-bsuids-are) ---------------------------------------------------------------------------------------------------------- A regular BSUID is scoped to one business portfolio. A parent BSUID is scoped to a parent BSUID account that contains multiple enrolled business portfolios. Format: US.ENT.11815799212886844830 Parent BSUIDs function like regular BSUIDs for messaging, but can be used by any business phone number in the enrolled portfolio set. Who Can Use Them[](https://dualhook.com/docs/contacts-bsuid-parent-user-ids#who-can-use-them) ---------------------------------------------------------------------------------------------- Meta's documentation describes this as an eligibility-based feature for managed businesses. If you have multiple business portfolios and need parent BSUIDs, ask your Meta point-of-contact to check eligibility and enrollment. After enrollment: * webhook payloads include both regular BSUIDs and parent BSUIDs; * users are identified by the same parent BSUID across enrolled portfolios; * each business portfolio keeps its own access control, billing, and administrative independence; * all portfolios enrolled in the same parent BSUID account are visible to each other through the Parent BSUID Accounts API. Payload Fields[](https://dualhook.com/docs/contacts-bsuid-parent-user-ids#payload-fields) ------------------------------------------------------------------------------------------ Parent BSUIDs appear with field names such as: | Field | Surface | | --- | --- | | `parent_user_id` | Contacts blocks, incoming message contacts, user preferences, group participants, contact book-adjacent payloads. | | `from_parent_user_id` | Incoming messages and user-initiated calling payloads. | | `recipient_parent_user_id` | Message and call status webhooks. | | `recipient_participant_parent_user_id` | Group message status webhooks. | | `to_parent_user_id` | Business-initiated call and app-echo payloads. | Only store these fields when present. Do not assume every customer or portfolio has parent BSUIDs enabled. Parent BSUID Account API[](https://dualhook.com/docs/contacts-bsuid-parent-user-ids#parent-bsuid-account-api) -------------------------------------------------------------------------------------------------------------- Use the Parent BSUID Accounts API to identify the parent account and enrolled portfolios: GET //parent-bsuid-accounts Response shape: { "parent_bsuid_account_id": "", "enrolled_business_portfolios": [\ "",\ ""\ ] } `parent_bsuid_account_id` is the shared parent account. `enrolled_business_portfolios` lists the portfolio IDs allowed to use that account's parent BSUIDs. Operational Rules[](https://dualhook.com/docs/contacts-bsuid-parent-user-ids#operational-rules) ------------------------------------------------------------------------------------------------ * Regular BSUIDs still work inside their original business portfolio. * Parent BSUIDs can be used by phone numbers across the enrolled portfolios. * Contact books remain scoped to individual business portfolios, even when portfolios share parent BSUIDs. * CRM systems should store both regular BSUID and parent BSUID when both are present. * Parent BSUID enrollment does not merge billing, permissions, or admin controls across portfolios. Limitations[](https://dualhook.com/docs/contacts-bsuid-parent-user-ids#limitations) ------------------------------------------------------------------------------------ Parent BSUIDs are not supported everywhere: * Contact Book API deletion requires a regular BSUID. * Block and unblock user requests do not support parent BSUIDs. * Payloads omit parent BSUID fields unless enrollment is enabled. Check endpoint-specific docs before assuming parent BSUIDs are accepted. Dualhook Boundary[](https://dualhook.com/docs/contacts-bsuid-parent-user-ids#dualhook-boundary) ------------------------------------------------------------------------------------------------ Dualhook does not enroll portfolios into parent BSUID accounts and does not manage parent BSUID account membership. If your business is eligible, enrollment is handled through Meta. Related ------- * [Meta BSUID, Usernames & Contact Book Transition Guide](https://dualhook.com/docs/contacts-bsuid) Overview of Meta's WhatsApp BSUID rollout: usernames, contact book behavior, parent BSUIDs, API recipient support, webhook changes, and migration timeline. * [BSUID Webhook Changes](https://dualhook.com/docs/contacts-bsuid-webhooks) WhatsApp webhook payload changes for BSUIDs, usernames, parent BSUIDs, optional phone-number fields, statuses, contacts, groups, calling, and Coexistence events. * [BSUID API and Sending Changes](https://dualhook.com/docs/contacts-bsuid-api-changes) How WhatsApp APIs use BSUIDs and parent BSUIDs in the recipient field for messages, Marketing Messages API, groups, block users, calling, and SIP. * [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) The top-level container for business assets in Meta and why it matters for WhatsApp. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # smb_message_echoes Webhook Explained: Coexistence App Messages, Edits, Revokes | Dualhook Browse Docs smb\_message\_echoes Webhook ============================ Field reference for WhatsApp smb\_message\_echoes webhooks: Business App sent-message echoes, edits, revokes, direct routing, and Dualhook handling. Looking for the high-level overview first? Start with [WhatsApp Coexistence](https://dualhook.com/whatsapp-coexistence) and then return here for the implementation details. `smb_message_echoes` is the [Coexistence](https://dualhook.com/whatsapp-coexistence) webhook field that tells your backend what staff sent from the WhatsApp Business app or a linked device, after that number was onboarded to Cloud API. Meta's reference documents the payload shape; what it does not spell out is that the field only exists for Coexistence numbers, that it also carries `edit` and `revoke` events for app messages, and that it is one of the fields a partner can route straight to your server instead of through their own infrastructure. In Dualhook setups it is direct-routed to your endpoint via Webhook Override and never stored by Dualhook. What smb\_message\_echoes Reports[](https://dualhook.com/docs/webhook-smb-message-echoes#what-smb_message_echoes-reports) -------------------------------------------------------------------------------------------------------------------------- This field lets your backend see business-side app activity, including: * messages sent from the WhatsApp Business app, * messages sent from a companion or linked device, * deleted messages (`revoke`), * edited messages (`edit`). Use it when your system needs to keep its internal conversation state aligned with messages that staff send outside your API workflow. smb\_message\_echoes vs message\_echoes[](https://dualhook.com/docs/webhook-smb-message-echoes#smb_message_echoes-vs-message_echoes) ------------------------------------------------------------------------------------------------------------------------------------- Meta has two echo fields and they answer different questions: | Field | What it echoes | Who needs it | | --- | --- | --- | | `message_echoes` | Messages the business sent **through the Cloud API**, from any app or tool with access to the number. | Teams that run more than one system against the same number and want one of them to see everything sent via API. | | `smb_message_echoes` | Messages sent **from the WhatsApp Business app or a linked device** on a Coexistence number, including edits and revokes. | Any backend that must stay in sync with what staff type on their phones. | If your number is not on Coexistence, `smb_message_echoes` never fires. If you only subscribed to `message_echoes` and staff replies from the app seem to vanish, this is the field you were missing. When It Fires[](https://dualhook.com/docs/webhook-smb-message-echoes#when-it-fires) ------------------------------------------------------------------------------------ Meta sends this field when an onboarded WhatsApp Business app user sends, edits, or revokes a message from the mobile app or linked device. Payload Shape[](https://dualhook.com/docs/webhook-smb-message-echoes#payload-shape) ------------------------------------------------------------------------------------ { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "changes": [\ {\ "field": "smb_message_echoes",\ "value": {\ "messaging_product": "whatsapp",\ "metadata": {\ "display_phone_number": "15550783881",\ "phone_number_id": "106540352242922"\ },\ "message_echoes": [\ {\ "from": "15550783881",\ "to": "16505551234",\ "id": "",\ "timestamp": "1739321024",\ "type": "text",\ "text": {\ "body": "Here's the info you requested."\ }\ }\ ]\ }\ }\ ]\ }\ ] } `type` works like normal WhatsApp message payloads: it names the message type and points to a matching object such as `text`, `image`, `video`, `document`, `revoke`, or `edit`. Revoke and Edit Events[](https://dualhook.com/docs/webhook-smb-message-echoes#revoke-and-edit-events) ------------------------------------------------------------------------------------------------------ For revoked app messages, Meta sends `type: "revoke"` with the original message ID. { "type": "revoke", "revoke": { "original_message_id": "" } } For edited app messages, Meta sends `type: "edit"` with the original message ID and the updated message object. { "type": "edit", "edit": { "original_message_id": "", "message": { "type": "image", "image": { "caption": "Updated image caption" } } } } Dualhook Handling[](https://dualhook.com/docs/webhook-smb-message-echoes#dualhook-handling) -------------------------------------------------------------------------------------------- Dualhook does not ingest, store, forward, or replay `smb_message_echoes` payloads. This field is direct-routed through Webhook Override, so Meta sends it to your configured endpoint. That routing is intentional: app-sent message echoes can contain message bodies and media metadata, so they stay out of Dualhook's storage boundary. If echoes do not arrive, check the active Webhook Override configuration and your endpoint logs; Dualhook has no copy to resend. Implementation Notes[](https://dualhook.com/docs/webhook-smb-message-echoes#implementation-notes) -------------------------------------------------------------------------------------------------- * Store echoes separately from API send statuses so you can distinguish "sent by staff in app" from "sent by backend". * Make handlers idempotent by `message_echoes[].id`. * For `revoke` and `edit`, update the original message referenced by `original_message_id`. * Validate `metadata.phone_number_id` and WABA ID before accepting the payload. * Be ready for BSUID-era changes in phone-number fields on related message webhooks. Related[](https://dualhook.com/docs/webhook-smb-message-echoes#related) ------------------------------------------------------------------------ * [Webhook Override](https://dualhook.com/docs/webhook-override) * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) * [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) * [BSUID Transition Guide](https://dualhook.com/docs/contacts-bsuid) Related ------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. * [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) How Coexistence mode works: using WhatsApp Business App and Cloud API on the same number. * [Meta BSUID, Usernames & Contact Book Transition Guide](https://dualhook.com/docs/contacts-bsuid) Overview of Meta's WhatsApp BSUID rollout: usernames, contact book behavior, parent BSUIDs, API recipient support, webhook changes, and migration timeline. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Reaction Messages | Dualhook Browse Docs Reaction Messages ================= Apply emoji reactions to WhatsApp user messages. Limitations (sent-only status, 30-day window, error 131009), how to remove a reaction, and inbound user reaction webhooks. What Reaction Messages Do[](https://dualhook.com/docs/service-messages-reactions#what-reaction-messages-do) ------------------------------------------------------------------------------------------------------------ Reaction messages let you apply an emoji to a message you've received from a WhatsApp user — the same single-emoji reaction users apply natively in WhatsApp. They're sent through the standard `/messages` endpoint with `type: "reaction"` and reference the original user message by its WhatsApp message ID (`wamid`). Reactions are a [service message](https://dualhook.com/docs/service-messages) type and require an open customer service window. Limitations[](https://dualhook.com/docs/service-messages-reactions#limitations) -------------------------------------------------------------------------------- * Only `sent` status webhooks fire for reactions — **no `delivered` and no `read` webhooks**. Treat acceptance by Cloud API as the deliverable signal. * You can only react to a message you have **received** — not to your own outbound messages. * If the target message is more than **30 days old**, has been deleted, doesn't correspond to any message in the chat thread, or is itself a reaction, your reaction won't be delivered. You receive a `messages` webhook with error code `131009`. * One reaction per message. Sending a new reaction on the same message replaces the existing one. * Multi-character emoji (skin-tone modifiers, ZWJ sequences) are supported but a single reaction is one emoji. Send a Reaction[](https://dualhook.com/docs/service-messages-reactions#send-a-reaction) ---------------------------------------------------------------------------------------- { "messaging_product": "whatsapp", "recipient_type": "individual", "to": "12015550123", "type": "reaction", "reaction": { "message_id": "wamid.HBgLMTY0NjcwNDM1OTUVAgASGBQzQUZCMTY0MDc2MUYwNzBDNTY5MAA=", "emoji": "😀" } } Two equivalent ways to specify the emoji: | Form | Example | Notes | | --- | --- | --- | | Unicode escape sequence | `"😀"` | Safe for any transport (no encoding gotchas) | | Literal emoji | `"😀"` | Requires UTF-8 throughout your serialization | Use whichever your stack handles cleanly. If you're constructing the JSON in a language that doesn't escape correctly by default, prefer the escape sequence. `message_id` is the `wamid` of the user's message you're reacting to. You'll have received it on the inbound `messages` webhook. Remove a Reaction[](https://dualhook.com/docs/service-messages-reactions#remove-a-reaction) -------------------------------------------------------------------------------------------- Send the same payload with `emoji` set to an empty string: { "messaging_product": "whatsapp", "to": "12015550123", "type": "reaction", "reaction": { "message_id": "wamid.HBgL...", "emoji": "" } } This removes any reaction you previously placed on that message. Inbound User Reactions[](https://dualhook.com/docs/service-messages-reactions#inbound-user-reactions) ------------------------------------------------------------------------------------------------------ When a user reacts to one of **your** outbound messages, you receive a `messages` webhook with `type: "reaction"`: { "messages": [\ {\ "from": "12015550123",\ "id": "wamid.HBgL...",\ "timestamp": "1702920965",\ "type": "reaction",\ "reaction": {\ "message_id": "wamid.HBgL...",\ "emoji": "👍"\ }\ }\ ] } `reaction.message_id` references your original message. `reaction.emoji` is the chosen emoji (literal, not escape sequence). When the user removes their reaction, you receive a similar webhook with `emoji` set to an empty string. In Dualhook setups this webhook arrives on the `messages` field and is delivered directly to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) — Dualhook does not ingest it. Use Cases[](https://dualhook.com/docs/service-messages-reactions#use-cases) ---------------------------------------------------------------------------- * Acknowledge a user message without composing a full reply (👍 / ✅). * Signal a message has been actioned (📦 for "shipped", 💸 for "refunded"). * Disambiguate "I saw this" from "I'm working on it" without the typing-indicator/read-receipt nuance. Reactions never substitute for a real reply on transactional issues — use them to cut noise on quick acknowledgements, not to avoid sending a status update the user is waiting for. Related ------- * [Service Messages](https://dualhook.com/docs/service-messages) Free-form messages inside an open customer service window: types, replies, TTL, and October 2026 pricing with 1,000 free messages per number each month. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 131051: Unsupported Message Type Fixed | Dualhook Browse Docs WhatsApp Error 131051: Unsupported Message Type =============================================== WhatsApp error 131051 means an unsupported message type — often inbound edited, deleted, poll, or unavailable messages hitting your webhook handler. What to do. > **TL;DR:** WhatsApp error 131051 means an unsupported message type was encountered. Meta's error-table `details` string is **"Unsupported message type."** Most teams meet 131051 **inbound**: when a user sends something the Cloud API can't represent (edited, deleted/revoked, poll, or otherwise unavailable messages), the webhook delivers a message with `type: unsupported` carrying error 131051. The fix is to handle these gracefully in your inbound handler. What Error 131051 Means[](https://dualhook.com/docs/whatsapp-error-131051#what-error-131051-means) --------------------------------------------------------------------------------------------------- 131051 covers message types the Cloud API does not support. On Meta's error-codes table the `details` value is: **"Unsupported message type."** In the inbound **unsupported messages** webhook, Meta's own example (reference page "Updated: May 21, 2026") shows the error object verbatim as `"code":131051,"title":"Message type unknown","error_data":{"details":"Message type is currently not supported."},"type":"unsupported"` — and in that example `unsupported.type` is `edit`, confirming edited messages surface this way. Possible `unsupported` subtypes Meta lists include `button`, `edit`, `gif`, `group_invite`, `interactive`, `location`, `order`, `poll_creation`, `poll_update`, `product`, and `reaction`, among others. Where You See It[](https://dualhook.com/docs/whatsapp-error-131051#where-you-see-it) ------------------------------------------------------------------------------------- Primarily **inbound, via webhook**. The notification arrives in `entry.changes.value.messages[]` with `type: unsupported` and an `errors[]` array containing code `131051`. Per Meta's messages webhook reference: incoming message errors appear in the `messages` array and these webhooks have `type` set to `unsupported`. It can also appear synchronously if you attempt to **send** a type the API doesn't support, but the inbound case dominates real-world demand. Note: these webhooks arrive as new inbound events, so the `id` won't match a message you sent. Common Causes[](https://dualhook.com/docs/whatsapp-error-131051#common-causes) ------------------------------------------------------------------------------- * A user **edited** a message (surfaces as `unsupported.type: edit`). * A user **deleted/revoked** a message — the Cloud API does not provide a delete status, so you receive an unsupported-type error instead. * The user sent a **poll**, **group invite**, **product**, GIF, or other type the Cloud API can't render. * **Coexistence / companion-device** limitations: messages from some unsupported devices arrive as unsupported (often related to error 131060, "message unavailable"). * Attempting to **send** a message `type` not supported by the endpoint. How to Fix It[](https://dualhook.com/docs/whatsapp-error-131051#how-to-fix-it) ------------------------------------------------------------------------------- 1. In your inbound handler, branch on `messages[].type`. When it equals `unsupported`, read `errors[0].code` (131051) and `errors[0].error_data.details`. 2. Do **not** crash or treat it as a normal message. Store a placeholder ("unsupported message received") and continue. 3. If you need the content, prompt the user via a supported channel/type to resend. 4. If 131051 came from a **send** attempt, switch to a supported message type (see Meta's Messages → message types reference). 5. Distinguish 131051 (unsupported type) from 131060 (message unavailable) since coexistence scenarios can produce either. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-131051#how-to-prevent-it) --------------------------------------------------------------------------------------- * Treat `type: unsupported` as a first-class case in your webhook router, not an exception. * Log the `unsupported.type` subtype so you can quantify how often edits, deletions, or polls occur. * For senders, validate message `type` against the supported list before calling the API. Related Errors[](https://dualhook.com/docs/whatsapp-error-131051#related-errors) --------------------------------------------------------------------------------- * [WhatsApp Error 131026](https://dualhook.com/docs/whatsapp-error-131026) — message undeliverable. * [WhatsApp Error 131008](https://dualhook.com/docs/whatsapp-error-131008) — missing required parameter (send-side). * [WhatsApp Webhook Override (Error 100)](https://dualhook.com/docs/whatsapp-error-100-webhook-override) * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-131051#faq) ----------------------------------------------------------- **Q: Why do I get 131051 webhooks that don't match any message I sent?** A: They're **inbound** events — a user edited, deleted, or sent an unsupported type. The `id` is the user's message, not yours. **Q: What's the exact inbound title and details?** A: Meta's example shows title `Message type unknown` and details `Message type is currently not supported.` **Q: Does a deleted message give a delivery status?** A: No — the Cloud API has no delete status, so you instead receive an unsupported-type message with 131051. **Q: Is 131051 the same as 131060?** A: Related but distinct: 131060 is "message unavailable" (common with coexistence/companion devices); 131051 is "unsupported message type." How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-131051#how-dualhook-helps) ----------------------------------------------------------------------------------------- Honest answer: 131051 is an **inbound-handler** matter, and inbound `messages` webhooks go straight from Meta to your endpoint — Dualhook routes via Meta's `override_callback_uri` and is **not** in the message path, so it never reads or stores the content of these messages. Your code must branch on `type: unsupported`. Dualhook's relevant role is webhook **routing and subscription health**: it helps ensure your endpoint is correctly subscribed and receiving the `messages` field so these events actually reach you. See [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) , [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) , and [WhatsApp Coexistence](https://dualhook.com/docs/coexistence) for the companion-device cases. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Business API Permissions | Dualhook Browse Docs WhatsApp Business API Permissions ================================= OAuth scopes, WABA task access, delegated Meta access, and troubleshooting missing WhatsApp Business API permissions. Context[](https://dualhook.com/docs/whatsapp-business-api-permissions#context) ------------------------------------------------------------------------------- Dualhook operates as an official Meta Tech Partner onboarding layer for WhatsApp Cloud API features. WhatsApp Business API permissions in this context mean the customer has granted the required Meta access so Dualhook can configure and operate those features on their behalf. There are two layers of access that both have to be in place: 1. **Graph API permissions** — the OAuth scopes the access token carries (e.g. `whatsapp_business_messaging`). 2. **Business asset task scopes** — what the system user behind that token is allowed to do on the specific [WABA](https://dualhook.com/docs/whatsapp-business-accounts) (e.g. `MANAGE_TEMPLATES`). A token with the right OAuth scopes will still fail with error `200` if the underlying system user has not been granted the required business asset access on the WABA being queried. What Permissions Enable[](https://dualhook.com/docs/whatsapp-business-api-permissions#what-permissions-enable) --------------------------------------------------------------------------------------------------------------- With proper permissions, Dualhook can: * Configure [webhook subscription and override](https://dualhook.com/docs/webhook-override) * Sync and manage [templates](https://dualhook.com/docs/templates) * Read [health](https://dualhook.com/docs/health-monitoring) and account signals * Perform [phone-number](https://dualhook.com/docs/phone-numbers) \-level operations needed for setup Graph API Permissions (OAuth Scopes)[](https://dualhook.com/docs/whatsapp-business-api-permissions#graph-api-permissions-oauth-scopes) --------------------------------------------------------------------------------------------------------------------------------------- Dualhook requests these Graph API permissions during [Embedded Signup](https://dualhook.com/docs/embedded-signup) : | Permission | Why Dualhook needs it | | --- | --- | | `whatsapp_business_management` | Account metadata, template management, phone-number listing, analytics, account-level webhook subscriptions | | `whatsapp_business_messaging` | Send messages and receive incoming-message + status webhooks | `business_management` is **not** requested by default. It is only relevant if Dualhook needs to programmatically read or modify your [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) directly, which is not part of the standard onboarding flow. The token Dualhook stores after Embedded Signup is a **Business Integration System User access token**, scoped to the customer that completed onboarding. It is encrypted server-side and never exposed through the dashboard or API. A per-connection `dh_live_...` key authorizes the [allowlisted Runtime API](https://dualhook.com/docs/runtime-api-reference) for messages, media, templates, health/status, profiles, safe phone settings, and QR codes. If Meta reports an expiry for an existing token, reauthorization is required; Dualhook does not apply the ordinary System User refresh flow to BISU tokens. Business Asset Task Scopes[](https://dualhook.com/docs/whatsapp-business-api-permissions#business-asset-task-scopes) --------------------------------------------------------------------------------------------------------------------- In addition to OAuth permissions, the system user behind the token must hold task-level access on the specific WABA. In Dualhook-managed onboarding, required task scopes typically include: | Scope | Purpose | | --- | --- | | `MANAGE` | General WABA management | | `DEVELOP` | Development and testing access | | `MANAGE_PHONE` | Phone number configuration | | `MANAGE_PHONE_ASSETS` | Phone number asset operations | | `MANAGE_TEMPLATES` | Template CRUD operations | | `MESSAGING` | Send and receive messages | Checking Access[](https://dualhook.com/docs/whatsapp-business-api-permissions#checking-access) ----------------------------------------------------------------------------------------------- You can confirm access in three places: ### In Dualhook[](https://dualhook.com/docs/whatsapp-business-api-permissions#in-dualhook) * Connection is active * Sync and health checks succeed * Template operations are allowed * The connection's **Health** tab lists the granted OAuth scopes under its "Permissions & token" check ### Via Dualhook diagnostics[](https://dualhook.com/docs/whatsapp-business-api-permissions#via-dualhook-diagnostics) The connection Health tab reports the granted OAuth scopes and WABA target IDs. Dualhook obtains these through Meta's server-side token debugger during Embedded Signup without revealing either the BISU token or the app credential. ### In Meta Business Settings[](https://dualhook.com/docs/whatsapp-business-api-permissions#in-meta-business-settings) * The correct app or system user is assigned to the target [WABA](https://dualhook.com/docs/whatsapp-business-accounts) * Required task scopes are present Navigate to **Meta Business Settings** > **Accounts** > **WhatsApp Accounts** > select your WABA > **Assigned People/Apps** to verify. If Access Is Missing[](https://dualhook.com/docs/whatsapp-business-api-permissions#if-access-is-missing) --------------------------------------------------------------------------------------------------------- Symptoms usually include: * Subscription or override setup fails * Template sync, create, or update fails * Health checks return authorization errors (code `10` or `200` — see [API Error Reference](https://dualhook.com/docs/api-errors) ) ### Resolution[](https://dualhook.com/docs/whatsapp-business-api-permissions#resolution) 1. Re-run the delegated onboarding/authorization flow (Embedded Signup). 2. Ensure the correct [Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) and WABA are selected. 3. Confirm required task scopes are granted before retrying operations. 4. If using a system user, verify it has the correct role and asset assignments. For onboarding details, see [Embedded Signup](https://dualhook.com/docs/embedded-signup) . For troubleshooting connection issues, see [Troubleshooting](https://dualhook.com/docs/troubleshooting) . Related ------- * [WhatsApp Business Accounts](https://dualhook.com/docs/whatsapp-business-accounts) What a WABA is, how it relates to phone numbers, and how ownership works. * [Embedded Signup](https://dualhook.com/docs/embedded-signup) How Dualhook uses Meta's Embedded Signup flow to connect WhatsApp Business Accounts via OAuth. * [WABA Integration](https://dualhook.com/docs/waba-integration) End-to-end WhatsApp Business Account integration: token validation, discovery, and subscription. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Utility Templates: Components, Pricing, and Specialty Types | Dualhook Browse Docs Utility Messages ================ Utility template messaging hub: components and buttons, specialty types, the October 2026 CSW billing change, volume tiers, content rules, and recategorization. What Counts as Utility[](https://dualhook.com/docs/utility-messages#what-counts-as-utility) -------------------------------------------------------------------------------------------- Utility messages are [template](https://dualhook.com/docs/templates) messages sent in response to a user action or request, with specific context about an active transaction, account, subscription, or service interaction. Examples: * Order confirmation with order ID * Delivery update with tracking details * Billing or account reminder tied to an existing service * Service alert linked to a current issue * Reservation confirmation * Appointment reminder Utility templates have **strict content requirements**, particularly around marketing material. If you create or update a utility template that includes promotional language, Meta will automatically re-categorize it as a marketing template — see [Template Category Reclassification](https://dualhook.com/docs/webhook-events#template-category-reclassification) . Why Utility Templates Matter[](https://dualhook.com/docs/utility-messages#why-utility-templates-matter) -------------------------------------------------------------------------------------------------------- * Utility templates are generally lower-cost than marketing templates. * Through September 30, 2026, utility templates are free during an active 24-hour customer service window (CSW). Meta will charge for them from October 1, 2026. See [Pricing → Free vs Billed Messages](https://dualhook.com/docs/pricing#free-vs-billed-messages) . * They provide clear operational updates that customers expect and value. * Utility messages have [volume tiers](https://dualhook.com/docs/pricing#volume-tiers) — higher monthly volume per market unlocks lower per-message rates. Utility Pages in This Section[](https://dualhook.com/docs/utility-messages#utility-pages-in-this-section) ---------------------------------------------------------------------------------------------------------- * [Location templates](https://dualhook.com/docs/utility-templates-location) — UTILITY (or MARKETING) templates with a map header and send-time coordinates * Call permission request — see the [section below](https://dualhook.com/docs/utility-messages#call-permission-request-utility) on this page Prerequisites[](https://dualhook.com/docs/utility-messages#prerequisites) -------------------------------------------------------------------------- * Create a template with category `UTILITY`. * Wait for `APPROVED` status. * Capture and retain valid [user opt-in for WhatsApp communication](https://dualhook.com/docs/utility-messages#user-opt-in-for-whatsapp-communication) . * Send using the standard Cloud API [template message flow](https://dualhook.com/docs/sending-template-messages) . User Opt-In for WhatsApp Communication[](https://dualhook.com/docs/utility-messages#user-opt-in-for-whatsapp-communication) ---------------------------------------------------------------------------------------------------------------------------- Meta's Business Messaging Policy requires that a person opts in before your business sends them a business-initiated message. That applies to utility templates as much as to marketing templates. An order confirmation sent without opt-in is still a policy violation, even though the content is transactional. A valid opt-in must make three things clear to the user: 1. They are agreeing to receive messages **over WhatsApp**, not just "notifications". 2. **Which business** will message them, using the name they will see on the sender. 3. **What kind** of messages they can expect. Separate consent for marketing from consent for order and account updates wherever you can. Opt-in can be collected anywhere you already talk to the customer: a checkbox on a checkout or account form, a keyword or reply inside an existing WhatsApp thread, SMS, IVR, or in person at a point of sale. Meta does not require a specific channel, but it does require that the user takes an explicit action. Pre-ticked boxes, purchase-implied consent, and imported lists do not qualify. Keep evidence. Store the source, timestamp, channel, and category for every opt-in so you can answer a Meta quality review or a customer complaint. Blocks and reports from users who never opted in are the fastest way to lose [messaging limits](https://dualhook.com/docs/capacity-quality-limits) . Dualhook does not collect or store opt-in on your behalf. Consent lives in your own systems, and the [messaging best practices](https://dualhook.com/docs/messaging-best-practices#opt-in-and-consent) page covers how to structure it. Supported Components[](https://dualhook.com/docs/utility-messages#supported-components) ---------------------------------------------------------------------------------------- Utility templates support the following component layout: | Component | Cardinality | Notes | | --- | --- | --- | | `header` | 0 or 1 | All header formats supported: `text`, `image`, `video`, `document`, `location` | | `body` | 1 (required) | Up to 1024 characters. Supports named or positional parameters | | `footer` | 0 or 1 | Up to 60 characters. Variables supported | | `buttons` | Up to 10 | See supported types below | | `call_permission_request` | 0 or 1 | Cannot be combined with other interactive components — see [Call Permission Request (Utility)](https://dualhook.com/docs/utility-messages#call-permission-request-utility) | For full component-level structural details, see [Template Elements](https://dualhook.com/docs/template-elements) . Supported Button Types[](https://dualhook.com/docs/utility-messages#supported-button-types) -------------------------------------------------------------------------------------------- Up to 10 buttons total, in any combination of these types: * `quick_reply` — predefined reply text the user can tap * `url` — opens a URL in the user's default browser * `phone_number` — taps to dial the configured business phone number * `copy_code` — copies a code to the user's clipboard (max 20 characters) * Call request — used in conjunction with the `call_permission_request` component (see below) Specialty Utility Templates[](https://dualhook.com/docs/utility-messages#specialty-utility-templates) ------------------------------------------------------------------------------------------------------ Beyond standard custom utility templates, Meta supports two specialty utility template types. Both are created through the same `//message_templates` endpoint by varying the `components` array. | Type | What it does | | --- | --- | | **Custom utility** | Standard structure: optional header (any format), required body, optional footer, up to 10 buttons | | **[Location template](https://dualhook.com/docs/utility-templates-location)
** | Map header that displays a specific location at send time. Real-time location is not supported. Specify lat/lng/name/address in the send call, not at template creation time. Categorized as UTILITY or MARKETING. | | **Call permission request** | Required body + `call_permission_request` component. Recipient grants or denies your business permission to call them. Categorized as UTILITY or MARKETING. See section below. | Call Permission Request (Utility)[](https://dualhook.com/docs/utility-messages#call-permission-request-utility) ---------------------------------------------------------------------------------------------------------------- Call permission request templates can be categorized as either `MARKETING` or `UTILITY`. The UTILITY variant is appropriate when you want to call a customer about an active transaction, account issue, or service interaction. curl 'https://graph.facebook.com///message_templates' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "order_support_call", "language": "en_US", "category": "UTILITY", "parameter_format": "named", "components": [\ {\ "type": "body",\ "text": "Hi {{first_name}}, we would like to call you to assist with your recent order. Our support team is ready to help.",\ "example": {\ "body_text_named_params": [\ { "param_name": "first_name", "example": "Pablo" }\ ]\ }\ },\ { "type": "call_permission_request" }\ ] }' Constraints: * Body text is required and must not be empty. * The `call_permission_request` component cannot be combined with other interactive components (no buttons, no quick replies, no URLs). * Only `MARKETING` and `UTILITY` templates can include this component. When sent, the recipient sees a permission prompt and can grant or deny your business permission to call them outside the customer service window. Send via the standard `/messages` endpoint with `parameter_format: named` (note `language.policy: deterministic` is recommended). Common Use Cases[](https://dualhook.com/docs/utility-messages#common-use-cases) -------------------------------------------------------------------------------- * Order and delivery lifecycle notifications * Account alerts and service-status updates * Reservation and appointment confirmations * Post-service feedback collection * Follow-up on a support interaction started in another channel * Opt-in and opt-out confirmations * Delivery location updates (use a [location template](https://dualhook.com/docs/utility-templates-location) ) Sending Flow[](https://dualhook.com/docs/utility-messages#sending-flow) ------------------------------------------------------------------------ 1. Create utility template (custom, location, or call-permission). 2. Wait for `APPROVED` status. 3. Send via [template message endpoint](https://dualhook.com/docs/sending-template-messages) — `POST //messages`. 4. Monitor [webhook statuses](https://dualhook.com/docs/messaging-webhook) and quality signals. Utility templates are sent through Cloud API only. They cannot be sent through [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) , which is marketing-only. Recategorization and Appeals[](https://dualhook.com/docs/utility-messages#recategorization-and-appeals) -------------------------------------------------------------------------------------------------------- Meta automatically reclassifies a utility template as marketing if the content contains promotional language or mixed intent. When this happens, pricing and messaging behavior follow marketing rules immediately, and you receive a `template_category_update` webhook — see [Webhook Events → Template Category Reclassification](https://dualhook.com/docs/webhook-events#template-category-reclassification) . To challenge a reclassification, use Meta business support channels (Dualhook does not file appeals on your behalf). Billing and Windows[](https://dualhook.com/docs/utility-messages#billing-and-windows) -------------------------------------------------------------------------------------- * Utility templates inside an open CSW: **free through September 30, 2026**, then billed from October 1. Before the change, the webhook uses `pricing.type: free_customer_service` and `pricing.category: utility`. * Utility templates outside an open CSW: billed at the utility rate for the recipient's country/region. * Volume tiers apply — higher monthly volume per market–category unlocks lower rates. See [Pricing → Volume Tiers](https://dualhook.com/docs/pricing#volume-tiers) . Best Practices[](https://dualhook.com/docs/utility-messages#best-practices) ---------------------------------------------------------------------------- * Keep utility templates factual, concise, and non-promotional. * Include concrete transaction references (order, booking, account, case IDs). * Do not add upsell or cross-sell wording in utility templates — Meta will reclassify them as marketing. * Use [marketing templates](https://dualhook.com/docs/marketing-messages) separately when the objective is promotion. * For physical-location updates (delivery, pickup, store), prefer [location templates](https://dualhook.com/docs/utility-templates-location) over text-only descriptions. Related ------- * [Location Templates](https://dualhook.com/docs/utility-templates-location) WhatsApp templates with a map header. Specify latitude, longitude, name, and address at send time. Useful for delivery updates, store finders, ride-hailing pickup and drop-off. * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. * [Marketing Messages](https://dualhook.com/docs/marketing-messages) Marketing template messaging through Dualhook's standard Cloud API runtime, plus boundaries for Meta's separate MM API product. * [Authentication Messages](https://dualhook.com/docs/authentication-messages) OTP and verification code delivery via WhatsApp: copy-code, one-tap, and zero-tap templates, linked device security, the 'I didn't request a code' webhook, and PendingIntent deprecation. * [Pricing](https://dualhook.com/docs/pricing) Dualhook subscription plans, what is included, and how pricing relates to Meta messaging fees. * [WhatsApp Pricing Changes 2026](https://dualhook.com/docs/whatsapp-pricing-changes-2026) Meta's 2026 WhatsApp pricing changes: Meta Business Agent billing, 1,000 free monthly service messages per number, chargeable service and in-window utility messages, and October rate cards. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Messaging Webhook | Dualhook Browse Docs Messaging Webhook ================= Real-time webhook events for inbound messages, delivery statuses, and errors. Looking for the high-level overview first? Start with [WhatsApp Webhook](https://dualhook.com/whatsapp-webhook) and then return here for the implementation details. Purpose[](https://dualhook.com/docs/messaging-webhook#purpose) --------------------------------------------------------------- Messaging webhooks are the real-time event stream for WhatsApp traffic. They include inbound user messages (`messages`), outbound delivery/read status changes (`statuses`), and messaging errors (`errors`). In Dualhook, message-path events are delivered by Meta directly to your endpoint through [Webhook Override](https://dualhook.com/docs/webhook-override) . Dualhook handles management-event processing separately. With Meta's early April 2026 BSUID rollout and later username launch, these webhooks include new BSUID fields and some existing fields become conditional. See the [BSUID webhook changes reference](https://dualhook.com/docs/contacts-bsuid-webhooks) for payload changes and migration guidance. Webhook Setup in Dualhook[](https://dualhook.com/docs/messaging-webhook#webhook-setup-in-dualhook) --------------------------------------------------------------------------------------------------- When a connection is subscribed, Dualhook configures the callback override server-side through Meta Graph API. Enter the callback URL and a verify token in the dashboard; Dualhook never reveals the Meta credential used for the subscription. Callback URL Requirements[](https://dualhook.com/docs/messaging-webhook#callback-url-requirements) --------------------------------------------------------------------------------------------------- Use a publicly reachable HTTPS endpoint: * Use standard TLS endpoint URLs. * Avoid underscores in hostnames. * Avoid custom port patterns — use standard HTTPS serving. Verification Handshake (GET)[](https://dualhook.com/docs/messaging-webhook#verification-handshake-get) ------------------------------------------------------------------------------------------------------- Meta verifies callback ownership with a challenge request: GET /webhooks/meta/messages?hub.mode=subscribe&hub.verify_token=&hub.challenge=1903260781 Return `HTTP 200` with the challenge value as the response body. Inbound Webhook POST Validation[](https://dualhook.com/docs/messaging-webhook#inbound-webhook-post-validation) --------------------------------------------------------------------------------------------------------------- This is the most common source of confusion in the Dualhook flow, so read this carefully before wiring up signature verification on your endpoint. In this section, **POST** means the inbound webhook POST that Meta sends to your callback URL. It does **not** mean outbound runtime calls such as `POST /v25.0/{PHONE_NUMBER_ID}/messages`; outbound sends use your per-connection `dh_live_...` key at `api.dualhook.com`. When you onboard a number through Dualhook's [Embedded Signup](https://dualhook.com/docs/embedded-signup) (the standard path, including [Coexistence](https://dualhook.com/docs/coexistence) ), the WhatsApp Business Account is subscribed to **Dualhook's Meta app**, not your own. Meta therefore signs `X-Hub-Signature-256` using **Dualhook's Meta App Secret**, which is shared at the Meta-app level across all our customers and cannot be exposed. So in the Dualhook-managed flow: * Your own `META_APP_SECRET` will **not** match the signature — your HMAC computation will be correct, but the secret is wrong. * The webhook verify token you configured in Dualhook is for the GET verification handshake only. It is **not** the POST signing secret and cannot be used to validate `X-Hub-Signature-256`. * Dualhook does **not** re-sign or add an `X-Dualhook-Signature` to message-path webhooks, because those payloads go from Meta directly to your endpoint — Dualhook is not in the message path. The recommended validation stack for message-path webhooks in the Dualhook-managed flow is therefore: 1. Verify the payload shape matches the WhatsApp webhook envelope (`object: "whatsapp_business_account"`, `entry[].changes[].field === "messages"`). Inbound messages, statuses, and failures appear inside `entry[].changes[].value`. 2. Validate `value.metadata.phone_number_id` against the `phone_number_id` you stored for this tenant/connection. Reject missing or unknown IDs for message-path events. 3. Validate the `entry[].id` (WABA ID) against the WABA you expect for this tenant. 4. Use a high-entropy, per-tenant webhook URL path (random segment, not guessable) and treat that path as a secret. Rotate it if it leaks. 5. Serve over HTTPS only, return `200` quickly, and reject unexpected methods. 6. For stronger sender authentication at the transport layer, enable [Meta webhook mTLS](https://dualhook.com/docs/webhooks#mutual-tls-mtls) where your infrastructure supports client certificate verification. Still keep the WABA and phone-number checks above, because mTLS proves the request came from Meta, not which tenant it belongs to. If you bring your own Meta app and subscribe it to the WABA yourself (a non-default flow that Dualhook does not manage for you), then `X-Hub-Signature-256` is signed with your Meta App Secret and standard HMAC verification on the raw request body applies. This is rare and is **not** what Embedded Signup configures. If cryptographic signing of message-path webhooks with a secret you control is a hard requirement for your compliance posture, contact support — it requires a different routing architecture than the standard Webhook Override flow. Webhook Envelope[](https://dualhook.com/docs/messaging-webhook#webhook-envelope) --------------------------------------------------------------------------------- All messaging webhook payloads use this outer structure: { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "messages",\ "value": {}\ }\ ]\ }\ ] } Inside `entry[].changes[].value`, expect `messages` for inbound events, `statuses` for outbound lifecycle updates, and `errors` for messaging failures. Inbound Text Message[](https://dualhook.com/docs/messaging-webhook#inbound-text-message) ----------------------------------------------------------------------------------------- { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "messages",\ "value": {\ "messaging_product": "whatsapp",\ "metadata": {\ "display_phone_number": "15550001234",\ "phone_number_id": "123456789012345"\ },\ "contacts": [\ {\ "profile": { "name": "Alex Example" },\ "wa_id": "12015550123"\ }\ ],\ "messages": [\ {\ "from": "12015550123",\ "id": "wamid.HBgLMTIwMTU1NTAxMjMVAgARGBI...",\ "timestamp": "1735939200",\ "type": "text",\ "text": { "body": "Hello from customer" }\ }\ ]\ }\ }\ ]\ }\ ] } Inbound Button Reply[](https://dualhook.com/docs/messaging-webhook#inbound-button-reply) ----------------------------------------------------------------------------------------- { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "messages",\ "value": {\ "messaging_product": "whatsapp",\ "metadata": {\ "display_phone_number": "15550001234",\ "phone_number_id": "123456789012345"\ },\ "messages": [\ {\ "from": "12015550123",\ "id": "wamid.HBgLMTIwMTU1NTAxMjMVAgARGBJ...",\ "timestamp": "1735939300",\ "type": "button",\ "button": {\ "text": "Track order",\ "payload": "track_order"\ },\ "context": {\ "id": "wamid.HBgLMTIwMTU1NTAxMjMVAgARGBI..."\ }\ }\ ]\ }\ }\ ]\ }\ ] } Outbound Status Update[](https://dualhook.com/docs/messaging-webhook#outbound-status-update) --------------------------------------------------------------------------------------------- { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "messages",\ "value": {\ "messaging_product": "whatsapp",\ "statuses": [\ {\ "id": "wamid.HBgLMTIwMTU1NTAxMjMVAgARGBI...",\ "status": "delivered",\ "timestamp": "1735939400",\ "recipient_id": "12015550123",\ "conversation": {\ "id": "d9f6f2f6d17f4b20b0",\ "origin": { "type": "utility" }\ },\ "pricing": {\ "billable": true,\ "pricing_model": "CBP",\ "category": "utility"\ }\ }\ ]\ }\ }\ ]\ }\ ] } Error Notification[](https://dualhook.com/docs/messaging-webhook#error-notification) ------------------------------------------------------------------------------------- { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "messages",\ "value": {\ "messaging_product": "whatsapp",\ "errors": [\ {\ "code": 131026,\ "title": "Message Undeliverable",\ "message": "Recipient is not reachable at this time.",\ "error_data": {\ "details": "The message could not be delivered to the user."\ }\ }\ ]\ }\ }\ ]\ }\ ] } Acknowledgment and Latency[](https://dualhook.com/docs/messaging-webhook#acknowledgment-and-latency) ----------------------------------------------------------------------------------------------------- * Return `HTTP 200 OK` as soon as payload authenticity and basic shape are verified. * Do heavy processing asynchronously after ack. * Keep median callback response time around sub-250 ms. * Keep slow responses (>1s) to a very small fraction of requests. Throughput Planning[](https://dualhook.com/docs/messaging-webhook#throughput-planning) --------------------------------------------------------------------------------------- Outbound sends produce multiple status callbacks (`sent`, `delivered`, `read`). Size your webhook capacity for roughly `3x outbound message rate + inbound message rate` during busy windows. If your endpoint does not return `200`, Meta retries delivery with exponential backoff. Retries can continue for multiple days, so idempotent handling is required. Processing Best Practices[](https://dualhook.com/docs/messaging-webhook#processing-best-practices) --------------------------------------------------------------------------------------------------- * Persist raw webhook payloads with an internal trace ID. * Deduplicate by `wamid` (and status tuple where relevant). * Separate inbound message consumers from status/error consumers. * Monitor webhook ack latency and non-`200` rates independently of business logic. * Send `status=read` via the [messages endpoint](https://dualhook.com/docs/messaging-overview) after your app accepts an inbound message. Related ------- * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) Management webhook events: template status, quality updates, account alerts, and forwarding. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Messaging Best Practices](https://dualhook.com/docs/messaging-best-practices) Opt-in, notification quality, contact validation, callback handling, and pre-launch checklist. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Authentication Best Practices: Security & UX | Dualhook Browse Docs Authentication Best Practices ============================= Security and UX best practices for WhatsApp authentication: identity hash binding for recycled phone protection, opt-in collection, isWhatsAppInstalled checks for Android and iOS, multi-app support. The Recycled-Phone Problem[](https://dualhook.com/docs/authentication-best-practices#the-recycled-phone-problem) ----------------------------------------------------------------------------------------------------------------- WhatsApp verifies phone-number ownership via a 6-digit registration code at sign-up, but **does not enforce continued ownership**. After registration, the WhatsApp account stays bound to the original device's WhatsApp installation, not to the phone number itself. When mobile carriers recycle a phone number to a new subscriber: * The **new owner** has the SIM and receives SMS to that number. * The **previous owner** may still hold the WhatsApp account tied to that number, on their old device. If your authentication flow treats "I sent a code to this phone number via WhatsApp" as equivalent to "I sent a code to the rightful account holder," you can deliver OTPs to the wrong person — even when the phone number itself is correct. This is most dangerous in **account recovery** flows where the WhatsApp OTP is the only authentication factor. Identity Hash Binding[](https://dualhook.com/docs/authentication-best-practices#identity-hash-binding) ------------------------------------------------------------------------------------------------------- Cloud API includes an identity-change check system to detect when the WhatsApp account behind a phone number has changed. The pattern: 1. User registers in your app and selects WhatsApp as their auth channel. 2. Send an initial verification OTP via WhatsApp. 3. From the send response (or status webhook), Meta returns an **identity hash** for the recipient's WhatsApp account. 4. Store the identity hash alongside the user's account record. 5. On every subsequent OTP send, include that stored identity hash on the send request. 6. If the underlying WhatsApp account has changed (recycled phone, account migrated to a new device, etc.), Meta blocks the send rather than delivering the OTP to a different account. Failed sends in this case surface as standard error responses on the Cloud API call, which your backend can handle by forcing re-verification through a different channel. This binding is more secure than SMS, where the carrier-level identity is not exposed to you and a recycled-number takeover is silent. Dualhook Stance: No Auth Content Retention[](https://dualhook.com/docs/authentication-best-practices#dualhook-stance-no-auth-content-retention) ------------------------------------------------------------------------------------------------------------------------------------------------ Authentication template sends use Dualhook's allowlisted outbound runtime, which forwards the request to Meta using the connection's encrypted authorization. Dualhook does not persist or body-log the OTP payload or response. Status webhooks, including identity-change-related errors, are routed by Webhook Override straight from Meta to your endpoint without Dualhook ingest. What this means in practice: * **Identity hash storage and replay are your responsibility.** Dualhook does not capture or persist identity hashes from your send responses. Store them on your user record, server-side. * **Identity-change errors surface through the runtime's unchanged Meta response and your direct status webhooks.** There is no Dualhook hook to "auto-block on identity change" because Meta already handles it when you replay the hash on the send call. * **Dualhook does not receive the inbound [`messages` webhook field](https://dualhook.com/docs/webhooks) **, including events like `DID_NOT_REQUEST_CODE` that some auth flows rely on for fraud detection. Those events reach you directly via Webhook Override. In short: auth security remains your responsibility. Dualhook securely relays the outbound request without content retention, while inbound auth-related events continue to flow directly from Meta to your endpoint. Pair Authentication Channels[](https://dualhook.com/docs/authentication-best-practices#pair-authentication-channels) --------------------------------------------------------------------------------------------------------------------- For sensitive flows (account recovery, password reset, large transactions), do not treat a single WhatsApp OTP as sufficient. Add at least one of: * A second factor over a different channel (email, push notification, hardware key). * A knowledge factor (security question, last-4 of card, prior transaction detail). * A device-trust signal (registered device fingerprint, recent successful login from same device). The OTP proves "someone with access to this WhatsApp account did the action," not "the account holder did the action." Anti-Phishing Controls[](https://dualhook.com/docs/authentication-best-practices#anti-phishing-controls) --------------------------------------------------------------------------------------------------------- WhatsApp disables **forwarding** of authentication template messages — recipients cannot forward an OTP to a third party from inside WhatsApp. Combined with end-to-end encryption between Cloud API and the user, this raises the cost of social-engineering an OTP off a victim. The flip side: don't include the OTP in any message body shown outside the authentication template (e.g. a follow-up text or notification preview). The forwarding restriction only applies to the authentication template itself. Do Not Trust Unofficial Clients[](https://dualhook.com/docs/authentication-best-practices#do-not-trust-unofficial-clients) --------------------------------------------------------------------------------------------------------------------------- WhatsApp cannot validate the security practices of unofficial WhatsApp clients (modded APKs, third-party reimplementations). If a user is on an unofficial client, OTP delivery is not guaranteed to be secure even though Cloud API delivers the message normally. You cannot detect this from your side. The mitigation is to keep WhatsApp as one of multiple offered channels rather than forcing it. Collect Explicit Opt-In[](https://dualhook.com/docs/authentication-best-practices#collect-explicit-opt-in) ----------------------------------------------------------------------------------------------------------- Per the WhatsApp Business Messaging Policy, you must collect opt-in **before** sending any authentication message — even an initial verification. A clean implementation: at sign-up or when the user first chooses WhatsApp as their auth channel, present a clear choice ("WhatsApp / SMS / email") rather than defaulting to WhatsApp. Document the choice. The selection is the opt-in. Show Installed Channels Only[](https://dualhook.com/docs/authentication-best-practices#show-installed-channels-only) --------------------------------------------------------------------------------------------------------------------- If you offer WhatsApp alongside other channels, only show WhatsApp when the device actually has WhatsApp installed. Otherwise users will pick WhatsApp by default and never receive the code. The check is cheap (Android intent query, iOS scheme check) and removes a class of "I never got the code" support tickets. See [Checking WhatsApp Installation: Android](https://dualhook.com/docs/authentication-best-practices#checking-whatsapp-installation-android) and [iOS](https://dualhook.com/docs/authentication-best-practices#checking-whatsapp-installation-ios) below. Support Both WhatsApp and Business App[](https://dualhook.com/docs/authentication-best-practices#support-both-whatsapp-and-business-app) ----------------------------------------------------------------------------------------------------------------------------------------- Two separate WhatsApp packages exist on Android: * `com.whatsapp` — consumer WhatsApp * `com.whatsapp.w4b` — WhatsApp Business app (some users use this exclusively) If you only check or broadcast to one, you'll miss users on the other. Always handle both — Meta's [OTP Android SDK](https://dualhook.com/docs/authentication-otp-handshake#the-otp-android-sdk-preferred) does this automatically. Be Ready for Immediate Delivery[](https://dualhook.com/docs/authentication-best-practices#be-ready-for-immediate-delivery) --------------------------------------------------------------------------------------------------------------------------- When using one-tap autofill, the OTP can arrive **before** your code-entry screen finishes loading — especially on slow networks. If your handler only listens after the screen is fully presented, you'll drop the code. Pattern: persist the received code in memory or local storage as soon as the receiver fires, then read from that storage when the entry screen is ready. Clear it after use. Confirm the WhatsApp Number, Not the SMS Number[](https://dualhook.com/docs/authentication-best-practices#confirm-the-whatsapp-number-not-the-sms-number) ---------------------------------------------------------------------------------------------------------------------------------------------------------- A user's "phone number" can mean different things on different SIMs and apps. The phone number registered with WhatsApp is not always the same as the SIM number used for SMS — common in: * Dual-SIM users (one for travel, one for home). * Users who registered WhatsApp on a number they no longer use for SMS. * Users who ported a number but kept WhatsApp on the old account. If your sign-up flow assumes the registration phone number is the WhatsApp number, you'll send OTPs to the wrong place. Always give the user a chance to confirm the number specifically used for WhatsApp before sending the OTP. If WhatsApp delivery is failing but the chosen channel is WhatsApp, suspect this scenario before suspecting the API. One WABA Per Business[](https://dualhook.com/docs/authentication-best-practices#one-waba-per-business) ------------------------------------------------------------------------------------------------------- Every business sending authentication templates must use **its own** WhatsApp Business Account and its own phone numbers. Sharing a WABA across separate business entities violates the WhatsApp Business Terms of Service and the WhatsApp Business Messaging Policy. Sharing also degrades the user experience — message threads, quality ratings, and policy enforcement are scoped to the WABA, not the underlying business identity. Checking WhatsApp Installation: Android[](https://dualhook.com/docs/authentication-best-practices#checking-whatsapp-installation-android) ------------------------------------------------------------------------------------------------------------------------------------------ Add to `AndroidManifest.xml` so the package query is allowed: With the OTP Android SDK (preferred): WhatsAppOtpHandler whatsAppOtpHandler = new WhatsAppOtpHandler(); if (whatsAppOtpHandler.isWhatsAppInstalled(context)) { // show WhatsApp option in your UI } Without the SDK: public boolean isWhatsAppInstalled(Context context) { return isWhatsAppInstalled(context, "com.whatsapp") || isWhatsAppInstalled(context, "com.whatsapp.w4b"); } private boolean isWhatsAppInstalled(Context context, String packageName) { Intent intent = new Intent(); intent.setPackage(packageName); intent.setAction("com.whatsapp.otp.OTP_REQUESTED"); return !context.getPackageManager().queryBroadcastReceivers(intent, 0).isEmpty(); } Either form returns true if **either** the consumer or business app is installed. Checking WhatsApp Installation: iOS[](https://dualhook.com/docs/authentication-best-practices#checking-whatsapp-installation-ios) ---------------------------------------------------------------------------------------------------------------------------------- let schemeURL = URL(string: "whatsapp://otp")! let isWhatsAppInstalled = UIApplication.shared.canOpenURL(schemeURL) Note: the iOS `canOpenURL` check requires the `whatsapp` URL scheme to be declared in your `Info.plist` `LSApplicationQueriesSchemes`. iOS authentication delivery uses copy-code only — there is no one-tap or zero-tap on iOS — but the installation check still gates whether it makes sense to offer WhatsApp as a channel at all. Related ------- * [Authentication Messages](https://dualhook.com/docs/authentication-messages) OTP and verification code delivery via WhatsApp: copy-code, one-tap, and zero-tap templates, linked device security, the 'I didn't request a code' webhook, and PendingIntent deprecation. * [OTP Handshake & Android SDK](https://dualhook.com/docs/authentication-otp-handshake) How the WhatsApp OTP handshake works for one-tap and zero-tap authentication templates: OTP Android SDK, manifest setup, eligibility checks, multi-app support, error codes, and PendingIntent deprecation. * [Compliance & Data Retention](https://dualhook.com/docs/compliance) Privacy boundary, configurable retention, CSV export, and organization isolation. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Media Messages | Dualhook Browse Docs Media Messages ============== Sending and receiving images, video, audio, documents, and stickers via Cloud API. Overview[](https://dualhook.com/docs/media-messages#overview) -------------------------------------------------------------- Media messages let you send non-text content through the same Cloud API [message endpoint](https://dualhook.com/docs/messaging-overview) : POST https://api.dualhook.com/v25.0//messages Supported outbound types: `image`, `video`, `audio`, `document`, `sticker`. In Dualhook architecture, your backend sends through the connection-scoped runtime. Inbound media webhooks are delivered directly to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) . Outbound and requested media stream through Dualhook without persistence or caching. Supported Formats and Size Limits[](https://dualhook.com/docs/media-messages#supported-formats-and-size-limits) ---------------------------------------------------------------------------------------------------------------- | Type | Typical supported formats | Max size | | --- | --- | --- | | Image | `jpeg`, `png` | 5 MB | | Audio | `aac`, `amr`, `mp3`, `m4a`, `ogg` (Opus) | 16 MB | | Video | `mp4`, `3gp` | 16 MB | | Document | `pdf`, `doc`, `docx`, `ppt`, `pptx`, `xls`, `xlsx`, `txt` | 100 MB | | Sticker | `webp` | Static 100 KB, animated 500 KB | * `document`, `image`, and `video` can include captions. * `audio` and `sticker` do not use captions. * Validate MIME type and file extension consistency before upload/send. Send Media by Media ID[](https://dualhook.com/docs/media-messages#send-media-by-media-id) ------------------------------------------------------------------------------------------ Recommended flow: upload media first to get a `media_id`, then send using `id`. { "messaging_product": "whatsapp", "to": "", "type": "image", "image": { "id": "", "caption": "" } } For document sends, include `filename`: { "messaging_product": "whatsapp", "to": "", "type": "document", "document": { "id": "", "filename": "", "caption": "" } } For upload instructions, see [Upload, Retrieve & Delete Media](https://dualhook.com/docs/media-upload) . Send Media by Public URL[](https://dualhook.com/docs/media-messages#send-media-by-public-url) ---------------------------------------------------------------------------------------------- { "messaging_product": "whatsapp", "to": "", "type": "image", "image": { "link": "https:///", "caption": "" } } URL requirements: * URL must be publicly reachable over HTTPS. * The endpoint must return the binary file directly. * `Content-Type` should match the real file type. Voice Messages vs Basic Audio[](https://dualhook.com/docs/media-messages#voice-messages-vs-basic-audio) -------------------------------------------------------------------------------------------------------- Audio messages have two presentations: | | Voice message | Basic audio | | --- | --- | --- | | Codec / format | `.ogg` encoded with OPUS codec, mono | Any supported audio format (`mp3`, `aac`, `m4a`, `amr`, etc.) | | Auto-download | Yes (≤ 512 KB) | No (user taps to download) | | Profile picture | Shows business profile image | Generic music icon | | Icon | Microphone | Music note (or microphone if file is OPUS-encoded `.ogg`) | | Auto-transcription | Available if user enabled "Automatic" voice transcripts | Available if file is OPUS-encoded `.ogg` and user enabled transcripts | | Recipient sees a play icon | Yes (≤ 512 KB) | Only if recipient has audio auto-download enabled and conditions are met (e.g. on Wi-Fi) | To send a voice message, set `voice: true` and provide an OPUS-encoded `.ogg` file: { "messaging_product": "whatsapp", "to": "12015550123", "type": "audio", "audio": { "id": "", "voice": true } } If you set `voice: true` with a non-OPUS file, voice-message transcription will silently fail. To send a regular audio file, omit `voice` or set it to `false`. Voice Played Status Webhook[](https://dualhook.com/docs/media-messages#voice-played-status-webhook) ---------------------------------------------------------------------------------------------------- Starting **March 17, 2026**, voice messages emit a new status webhook the **first time** a recipient plays a voice message you sent. The status is `played` and arrives via the standard messaging-status webhook envelope. { "statuses": [\ {\ "id": "wamid.HBgL...",\ "status": "played",\ "timestamp": "1742227200",\ "recipient_id": "12015550123",\ "conversation": { "id": "" },\ "pricing": { /* same shape as other status webhooks */ }\ }\ ] } Only the first play triggers the webhook — subsequent replays do not. The webhook only fires for voice messages (audio with `voice: true`); basic audio messages do not get a `played` status. In Dualhook setups, status webhooks are delivered directly to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) — Dualhook does not ingest them. Add `played` to the set of status values your handler recognizes if you want to track voice-message engagement. Inbound Media Webhooks[](https://dualhook.com/docs/media-messages#inbound-media-webhooks) ------------------------------------------------------------------------------------------ Inbound media from users includes a media object with an ID. Your backend should use that media ID to retrieve the binary from Meta. { "messages": [\ {\ "from": "",\ "id": "",\ "timestamp": "",\ "type": "image",\ "image": {\ "id": "",\ "mime_type": "image/jpeg",\ "sha256": "",\ "caption": ""\ }\ }\ ] } Error Handling[](https://dualhook.com/docs/media-messages#error-handling) -------------------------------------------------------------------------- * Message API acceptance does not guarantee recipient delivery. * Final outcomes arrive via [status webhooks](https://dualhook.com/docs/messaging-webhook) . * Prefer `media_id` sends for higher reliability. * Keep retry logic with exponential backoff for `429` and `5xx`. * Avoid tight retry loops for repeated `4xx` validation failures. Related ------- * [Upload, Retrieve & Delete Media](https://dualhook.com/docs/media-upload) Media lifecycle: upload files, retrieve download URLs, and delete media objects. * [Messaging Overview](https://dualhook.com/docs/messaging-overview) Core messaging endpoint, text messages, formatting, delivery statuses, and common errors. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Business Platform Webhook Fields Reference | Dualhook Browse Docs Webhook Subscription Fields =========================== Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting. What Webhooks Do[](https://dualhook.com/docs/webhooks#what-webhooks-do) ------------------------------------------------------------------------ Webhooks are HTTP requests with JSON payloads that Meta sends to your server in response to events on the WhatsApp Business Platform — incoming messages, message status updates, template approvals, account alerts, calling events, settings changes, and more. Each webhook arrives on a specific **subscription field**. You subscribe your app to the fields you care about; Meta sends the relevant events to your configured callback URL. For the inbound message envelope and inbound webhook POST validation model, see [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) . For the management events Dualhook ingests and forwards, see [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) . For routing message-path events directly to your endpoint, see [Webhook Override](https://dualhook.com/docs/webhook-override) . Where They Go in Dualhook[](https://dualhook.com/docs/webhooks#where-they-go-in-dualhook) ------------------------------------------------------------------------------------------ Dualhook routes webhooks two ways depending on the field: | Field | Routing | | --- | --- | | `messages`, `smb_message_echoes`, `history`, `smb_app_state_sync` | Direct to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override)
. Dualhook does not ingest these. | | Normalized management fields (`account_update`, `account_review_update`, `business_capability_update`, `message_template_status_update`, `message_template_quality_update`, `phone_number_quality_update`, `phone_number_name_update`, `template_category_update`, `security`, `account_alerts`) | App callback → Dualhook → forwarded to your endpoint with `X-Dualhook-*` headers | | Fields not normalized by Dualhook today (`message_template_components_update`, `account_settings_update`, `calls`, `user_preferences`, `partner_solutions`, `payment_configuration_update`, `automatic_events`) | Not first-class Dualhook dashboard events. Subscribe your own app or endpoint if you need them. | Dualhook ingests normalized management events for state tracking (so the dashboard stays in sync with Meta) and forwards them to you. For Coexistence onboarding, Dualhook triggers Meta's one-time `history` and `smb_app_state_sync` sync requests after Webhook Override is accepted. Meta delivers any resulting payloads directly to your endpoint; the trigger acceptance is not a delivery guarantee. Subscribing to Fields[](https://dualhook.com/docs/webhooks#subscribing-to-fields) ---------------------------------------------------------------------------------- Once your webhook endpoint is configured, subscribe your app to individual fields: * **App Dashboard** → **WhatsApp** → **Configuration** panel. * Or, for apps created via the "Connect with customers through WhatsApp" use case: **App Dashboard** → **Use cases** → **Customize** → **Configuration**. You can also override the callback URL per WABA or per business phone number — useful when partners want unique endpoints for each onboarded customer. See [Webhook Override](https://dualhook.com/docs/webhook-override) . Permissions[](https://dualhook.com/docs/webhooks#permissions) -------------------------------------------------------------- | Permission | Required for | | --- | --- | | `whatsapp_business_messaging` | The `messages` webhook field | | `whatsapp_business_management` | All other webhook fields | If you're a partner needing these permissions to serve business customers, you must be approved for **advanced access** via App Review before customers can grant the permissions during onboarding. See [WhatsApp Business API Permissions](https://dualhook.com/docs/whatsapp-business-api-permissions) . All Subscription Fields[](https://dualhook.com/docs/webhooks#all-subscription-fields) -------------------------------------------------------------------------------------- | Field | What it notifies you of | | --- | --- | | `messages` | Inbound messages from users + outbound message status updates (sent / delivered / read / failed). The "main" field. | | [`message_template_status_update`](https://dualhook.com/docs/webhook-message-template-status-update) | Template review outcome — APPROVED / REJECTED / FLAGGED / etc. | | [`message_template_quality_update`](https://dualhook.com/docs/webhook-message-template-quality-update) | Template quality score changes | | [`message_template_components_update`](https://dualhook.com/docs/webhook-message-template-components-update) | Changes to a template's components (rare; mostly Meta-side reclassification) | | [`template_category_update`](https://dualhook.com/docs/webhook-template-category-update) | Template category reclassification (e.g. UTILITY → MARKETING) | | `phone_number_quality_update` | Phone number quality rating + tier changes | | `phone_number_name_update` | Display name verification outcomes | | [`account_update`](https://dualhook.com/docs/webhook-account-update) | Partner-led business verification, auth-international rate eligibility, primary business location, WABA sharing, policy violations, offboarding, reconnection, deletion. Also carries [pricing tier updates](https://dualhook.com/docs/webhook-events#account-update-events)
and MM API onboarding events. | | [`account_alerts`](https://dualhook.com/docs/webhook-account-alerts) | Messaging limit changes, business profile changes, OBA status changes | | [`account_review_update`](https://dualhook.com/docs/webhook-account-review-update) | WABA reviewed against policy guidelines | | [`business_capability_update`](https://dualhook.com/docs/webhook-business-capability-update) | Capability changes (messaging limits, phone number caps, etc.) | | `account_settings_update` | Phone number settings changed — currently only [calling settings](https://dualhook.com/docs/calling-configuration#settings-webhook-account_settings_update) | | [`security`](https://dualhook.com/docs/webhook-security) | Security setting changes on a phone number | | `user_preferences` | Marketing-message stop / resume actions by users | | `partner_solutions` | Multi-Partner Solution status changes | | `payment_configuration_update` | Payment config changes for Payments API India and Brazil | | `automatic_events` | Detected purchase / lead events on Click-to-WhatsApp ad chats (opt-in feature) | | `calls` | All [calling events](https://dualhook.com/docs/calling-api-reference#webhooks-summary)
— call connect, status, terminate. Required for Calling API users not on SIP. | | [`history`](https://dualhook.com/docs/webhook-history) | One-time chat history sync for WhatsApp Business app users onboarded via Coexistence. Dualhook requests the sync after onboarding; Meta routes the payload directly to your endpoint if the business shared history. | | [`smb_app_state_sync`](https://dualhook.com/docs/webhook-smb-app-state-sync) | Contact sync for WhatsApp Business app users onboarded via Coexistence. Dualhook requests the initial sync after onboarding; Meta routes this directly to your endpoint via Webhook Override. | | [`smb_message_echoes`](https://dualhook.com/docs/webhook-smb-message-echoes) | Echoes of messages sent via the WhatsApp Business app or a linked device by an onboarded SMB customer. | The `messages` field carries many sub-types (text / image / interactive / button reply / call\_permission\_reply / etc.) — your handler should switch on `messages[].type` and `messages[].interactive.type` to route them. Override Webhooks[](https://dualhook.com/docs/webhooks#override-webhooks) -------------------------------------------------------------------------- Use a different webhook callback URL for specific subscription fields on a WABA or phone number — see [Webhook Override](https://dualhook.com/docs/webhook-override) . Useful for testing or for partners that want per-customer endpoints. In Dualhook setups, override is how message-path and supported Coexistence fields bypass Dualhook and go straight to your endpoint. Payload Size[](https://dualhook.com/docs/webhooks#payload-size) ---------------------------------------------------------------- Webhook payloads can be **up to 3 MB**. Larger payloads are rejected at the source — Meta won't send them. Delivery Retries[](https://dualhook.com/docs/webhooks#delivery-retries) ------------------------------------------------------------------------ If your endpoint returns a non-200 status code, or if delivery fails for another reason, Meta retries with **decreasing frequency for up to 7 days**. Retries are sent to **all apps** subscribed to the field on the WABA — so if you have multiple apps on the same WABA, each gets its own retry cycle, which can produce duplicate notifications for already-handled events. Idempotency at your endpoint is mandatory. For calling-related webhooks, the retry policy is shorter (Meta hasn't published exact numbers) — check the `timestamp` field and skip stale events. Mutual TLS (mTLS)[](https://dualhook.com/docs/webhooks#mutual-tls-mtls) ------------------------------------------------------------------------ Webhooks support mTLS for added authentication. Recommended over IP allowlisting because Meta's IP ranges change periodically. See Graph API's mTLS for webhooks reference for setup. mTLS is **not supported for SIP signaling** — see [SIP Configuration](https://dualhook.com/docs/calling-sip#tls-certificate-requirements) for SIP-specific TLS requirements. IP Allowlisting[](https://dualhook.com/docs/webhooks#ip-allowlisting) ---------------------------------------------------------------------- If you must allowlist by IP, get the current Meta address space: whois -h whois.radb.net -- '-i origin AS32934' | grep '^route' | awk '{print $2}' | sort Or download the geofeed CSV from Meta's webhook docs. As of mid-2024 this collapses to ~23 IPv4 prefixes. **Meta changes IPs periodically** — set up monitoring to refresh your allowlist, or switch to mTLS to avoid the moving target. Troubleshooting[](https://dualhook.com/docs/webhooks#troubleshooting) ---------------------------------------------------------------------- If you're not receiving webhooks: 1. Confirm your endpoint accepts requests and returns 200. 2. Send a test payload from **App Dashboard → WhatsApp → Configuration** to verify reachability. 3. Confirm the field is subscribed and your app has the right permission (`messages` needs `whatsapp_business_messaging`; everything else needs `whatsapp_business_management`). 4. Check IP allowlist or mTLS configuration. 5. Use Dualhook's connection **Debug** tab to inspect Meta's view of your subscribed\_apps + webhook config. For Webhook Override / inbound webhook POST validation specifics, see [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) . Related ------- * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) Management webhook events: template status, quality updates, account alerts, and forwarding. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Messaging Overview](https://dualhook.com/docs/messaging-overview) Core messaging endpoint, text messages, formatting, delivery statuses, and common errors. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp QR Codes & Short Links API | Dualhook Browse Docs QR Codes & Short Links ====================== Create, list, update, and delete WhatsApp QR codes and short links via the Business Management API. Each code prefills a message in the chat — useful for campaigns, signage, and tenant-specific entry points. What QR Codes and Short Links Do[](https://dualhook.com/docs/qr-codes#what-qr-codes-and-short-links-do) -------------------------------------------------------------------------------------------------------- A WhatsApp QR code or short link routes someone directly into a chat with your business — no need for them to type your phone number. Each code generates **both** a scannable QR image and a short URL of the form `https://wa.me/message/`. The code can be programmed with a **prefilled message** (up to 140 chars). When the user scans or taps, the chat opens with that text already populated in the input field — they tap send to start the conversation. You manage QR codes through WhatsApp Manager or the connection-scoped [`dh_live_` Runtime API](https://dualhook.com/docs/runtime-api-reference#qr-codes) . Runtime paths preserve Meta's phone-edge shape but derive the authorized phone ID from the selected connection. Limitations[](https://dualhook.com/docs/qr-codes#limitations) -------------------------------------------------------------- * **2,000 QR codes / short links per business phone number** maximum. * **140 character** prefilled message limit. * **No analytics** — Meta deliberately does not log scan counts or related data, for user privacy. * Codes do **not** expire automatically — you delete them when done. Create a QR Code[](https://dualhook.com/docs/qr-codes#create-a-qr-code) ------------------------------------------------------------------------ POST //message_qrdls { "prefilled_message": "Cyber Monday", "generate_qr_image": "SVG" } | Field | Notes | | --- | --- | | `prefilled_message` | Text that appears prefilled in the user's input when they open the chat. Max 140 chars. | | `generate_qr_image` | `SVG` or `PNG`. SVG is recommended for print materials (scalable). | Response: { "code": "4O4YGZEG3RIVE1", "prefilled_message": "Cyber Monday", "deep_link_url": "https://wa.me/message/4O4YGZEG3RIVE1", "qr_image_url": "https://scontent-iad3-2.xx.fbcdn.net/..." } `code` is the persistent identifier. `deep_link_url` is the human-shareable short link. `qr_image_url` is the scannable image asset hosted by Meta. List QR Codes[](https://dualhook.com/docs/qr-codes#list-qr-codes) ------------------------------------------------------------------ GET //message_qrdls Returns all QR codes on the phone number: { "data": [\ {\ "code": "4O4YGZEG3RIVE1",\ "prefilled_message": "Cyber Monday",\ "deep_link_url": "https://wa.me/message/4O4YGZEG3RIVE1"\ },\ {\ "code": "WOMVT6TJ2BP7A1",\ "prefilled_message": "Tell me more about your production workshop",\ "deep_link_url": "https://wa.me/message/WOMVT6TJ2BP7A1"\ }\ ] } The `qr_image_url` is **not** included in the list response — fetch the single QR code to get the image, or generate it again at create time. Get a Single QR Code[](https://dualhook.com/docs/qr-codes#get-a-single-qr-code) -------------------------------------------------------------------------------- GET //message_qrdls/ { "data": [\ {\ "code": "4O4YGZEG3RIVE1",\ "prefilled_message": "Cyber Monday",\ "deep_link_url": "https://wa.me/message/4O4YGZEG3RIVE1"\ }\ ] } Update a QR Code[](https://dualhook.com/docs/qr-codes#update-a-qr-code) ------------------------------------------------------------------------ POST //message_qrdls { "code": "4O4YGZEG3RIVE1", "prefilled_message": "Cyber Tuesday" } Updates the prefilled message in place — the `code` stays the same, so any existing scans / short links continue to work but with the new message text. Response includes the updated message: { "code": "4O4YGZEG3RIVE1", "prefilled_message": "Cyber Tuesday", "deep_link_url": "https://wa.me/message/4O4YGZEG3RIVE1" } Delete a QR Code[](https://dualhook.com/docs/qr-codes#delete-a-qr-code) ------------------------------------------------------------------------ DELETE //message_qrdls/ { "success": true } After deletion, anyone scanning or clicking the code/link sees an "expired" error message. User Experience[](https://dualhook.com/docs/qr-codes#user-experience) ---------------------------------------------------------------------- | User scenario | What they see | | --- | --- | | Scans an active QR code | Chat opens with the prefilled message in the input field | | Clicks an active short link on mobile | Same — chat opens with prefilled message | | Clicks short link on desktop browser | Desktop client launches; if not installed, user is prompted to install it | | Scans / taps a deleted code | "This QR code \[short link\] has expired" error message | | Scans / taps a code for a business they previously blocked | Prompted to unblock the business to continue | Best Practices[](https://dualhook.com/docs/qr-codes#best-practices) -------------------------------------------------------------------- * **Use SVG** for print materials so the code scales without quality loss. * **Don't customize the QR's appearance** (colors, embedded logos) — preserves scan reliability. If you must, do it post-export with QR-aware tools that respect error correction zones. * **Use one code per language / locale.** You're responsible for routing users to the right code based on geography. * **Track campaign attribution out of band.** Since Meta provides no analytics, embed campaign markers in the prefilled message itself (e.g. "Cyber Monday code: CYB42") if you need to know which scan triggered which conversation. vs Plain wa.me Links[](https://dualhook.com/docs/qr-codes#vs-plain-wame-links) ------------------------------------------------------------------------------- A plain `wa.me/?text=Hello` link works without the API — just click-through to a chat with prefilled text. The API-managed QR codes / short links add: | | wa.me/`?text=...` | API-managed (`wa.me/message/`) | | --- | --- | --- | | Editable after creation | No (URL is the message) | Yes (POST to update) | | Deletable | No | Yes | | Phone number masked in URL | No | Yes | | QR image generation | DIY | Meta-hosted | | Per-tenant tracking | Use a different `text` per tenant | Use a different `code` per tenant | | Limit | Unlimited | 2,000 per phone number | For one-off campaigns, plain `wa.me` is fine. For tenant-specific or campaign-managed codes that need editing, use the API. Related ------- * [Phone Numbers](https://dualhook.com/docs/phone-numbers) Managing phone numbers in WhatsApp Business Platform: adding, removing, and limits. * [Marketing Messages](https://dualhook.com/docs/marketing-messages) Marketing template messaging through Dualhook's standard Cloud API runtime, plus boundaries for Meta's separate MM API product. * [Call Buttons & Deep Links](https://dualhook.com/docs/calling-call-buttons) Drive WhatsApp calls from messages and links: voice\_call interactive messages, voice\_call template buttons, wa.me/call/ deep links with biz\_payload tracking. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Contacts & Location Messages | Dualhook Browse Docs Contacts & Location Messages ============================ Sending and receiving contact cards and location messages via Cloud API. Overview[](https://dualhook.com/docs/contacts-location#overview) ----------------------------------------------------------------- Contacts and location are standard Cloud API message types sent through the same [messaging endpoint](https://dualhook.com/docs/messaging-overview) . Both are free-form message types and must follow the standard customer service window policy. Send Location Messages[](https://dualhook.com/docs/contacts-location#send-location-messages) --------------------------------------------------------------------------------------------- Set `type` to `location` and provide latitude, longitude, name, and address: { "messaging_product": "whatsapp", "to": "", "type": "location", "location": { "latitude": 37.7790262, "longitude": -122.4199061, "name": "", "address": "" } } Send Contacts Messages[](https://dualhook.com/docs/contacts-location#send-contacts-messages) --------------------------------------------------------------------------------------------- Set `type` to `contacts` and provide one or more contacts. The `name.formatted_name` field is required, plus at least one of `first_name`, `last_name`, `middle_name`, `suffix`, or `prefix`. { "messaging_product": "whatsapp", "to": "", "type": "contacts", "contacts": [\ {\ "name": {\ "formatted_name": "",\ "first_name": "",\ "last_name": ""\ },\ "phones": [\ { "phone": "", "type": "WORK" }\ ],\ "emails": [\ { "email": "", "type": "WORK" }\ ],\ "org": {\ "company": "",\ "title": ""\ }\ }\ ] } Inbound Location Webhook[](https://dualhook.com/docs/contacts-location#inbound-location-webhook) ------------------------------------------------------------------------------------------------- When a user shares a location, it arrives as a `messages` webhook with `type="location"`: { "messages": [\ {\ "from": "",\ "id": "",\ "timestamp": "",\ "type": "location",\ "location": {\ "latitude": 37.7790262,\ "longitude": -122.4199061,\ "name": "",\ "address": ""\ }\ }\ ] } Inbound Contact Card Webhook[](https://dualhook.com/docs/contacts-location#inbound-contact-card-webhook) --------------------------------------------------------------------------------------------------------- When a user sends a contact card, it arrives with `type="contacts"`: { "messages": [\ {\ "from": "",\ "id": "",\ "timestamp": "",\ "type": "contacts",\ "contacts": [\ {\ "name": {\ "formatted_name": "",\ "first_name": ""\ },\ "phones": [\ { "phone": "", "wa_id": "", "type": "CELL" }\ ]\ }\ ]\ }\ ] } Blocking and Unblocking Contacts[](https://dualhook.com/docs/contacts-location#blocking-and-unblocking-contacts) ----------------------------------------------------------------------------------------------------------------- The Dualhook runtime does not expose Meta's Block Users API. If this operation is required, manage it through your own separately reviewed Meta app and customer-owned authorization: * `POST //block_users` — block * `DELETE //block_users` — unblock * `GET //block_users` — list blocked A blocked user cannot message that business number, and the business number cannot send messages to blocked users. A `dh_live_...` key cannot call these endpoints. Related ------- * [Messaging Overview](https://dualhook.com/docs/messaging-overview) Core messaging endpoint, text messages, formatting, delivery statuses, and common errors. * [Meta BSUID, Usernames & Contact Book Transition Guide](https://dualhook.com/docs/contacts-bsuid) Overview of Meta's WhatsApp BSUID rollout: usernames, contact book behavior, parent BSUIDs, API recipient support, webhook changes, and migration timeline. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 100: Invalid Parameter Fix | Dualhook Browse Docs WhatsApp Error 100: Invalid Parameter ===================================== WhatsApp error 100 means Meta rejected a value or field in your request. Read error\_data.details, fix the recipient format, message type, or template component, and resend. > **TL;DR:** WhatsApp error `100` ("Invalid parameter") means Meta understood your request but rejected one of its values or fields. Check the endpoint, parameter names, values, and any error subcode before retrying. Read `message` and `error_data.details` when present for the specific cause. If you got error 100 while **subscribing a webhook**, that is a different situation covered on [Webhook Error 100](https://dualhook.com/docs/whatsapp-error-100-webhook-override) > . What Error 100 Means[](https://dualhook.com/docs/whatsapp-error-100#what-error-100-means) ------------------------------------------------------------------------------------------ Meta's Graph API uses code `100` for any parameter-level validation failure: a field Meta does not accept, a value in the wrong format, a value that refers to something that does not exist, or a field that does not fit the message type. The HTTP status is `400`. The code alone does not rule out object-access problems: an unsupported-object response can mean the ID is wrong, the object was deleted, or the caller cannot access it. Because code `100` covers so many cases, the useful information is in the accompanying text: { "error": { "message": "(#100) Invalid parameter", "type": "OAuthException", "code": 100, "error_data": { "messaging_product": "whatsapp", "details": "Parameter 'to' is not a valid phone number" }, "fbtrace_id": "..." } } This is an illustrative response. Log the full error object, including `error_data.details` when present; it is not included in every error 100 response. Where You See It[](https://dualhook.com/docs/whatsapp-error-100#where-you-see-it) ---------------------------------------------------------------------------------- An immediate API error on the send or management call. On the Dualhook Runtime API it is relayed unchanged, with Meta's `code`, `error_data`, and `fbtrace_id` intact and an `X-Dualhook-Request-Id` added. Also monitor failure statuses in webhooks: an accepted send response does not guarantee delivery. Runtime health reports cover HTTP request failures, not later delivery failures. Common Causes[](https://dualhook.com/docs/whatsapp-error-100#common-causes) ---------------------------------------------------------------------------- * **Recipient format.** An incomplete or incorrect international phone number. Using country code plus digits avoids formatting ambiguity; a leading `+` alone does not establish the cause of error 100. * **Message type mismatch.** `"type":"text"` with a `template` object, or `"type":"template"` without one. * **Unsupported or misspelled field.** A field from an older API version, a typo such as `messaging_prodcut`, or a component key Meta does not recognise. * **Template component shape.** A `parameters` array with the wrong item type for the component, or a named parameter that does not match the template variable name. * **Media reference.** An inaccessible or expired media ID. Code `100` with subcode `33` is an unsupported-object response; verify the ID, object type, and access. * **Interactive payload.** More buttons than allowed, a button `id` over 256 characters, or a list with an empty section. * **Template creation rules.** On `POST /message_templates`, a `2388xxx` subcode names the specific content or format rule that failed. How to Fix It[](https://dualhook.com/docs/whatsapp-error-100#how-to-fix-it) ---------------------------------------------------------------------------- 1. Read `error_data.details` and, if present, `error_subcode`. These can identify the parameter or explain why an object is inaccessible. 2. Compare the request body against the Cloud API reference for that message or object type. Field names are case-sensitive and version-specific. 3. For `to`, send only digits in international format: country code followed by the number, no `+`, no spaces. 4. For templates, confirm the component types and parameter counts match the approved template exactly; a mismatch on count is reported as [`132000`](https://dualhook.com/docs/whatsapp-error-132000) rather than `100`, but a wrong parameter type inside a component often comes back as `100`. 5. Fix the request and resend. Error 100 is not retryable by itself; the same request will fail the same way. Subcodes That Change the Meaning[](https://dualhook.com/docs/whatsapp-error-100#subcodes-that-change-the-meaning) ------------------------------------------------------------------------------------------------------------------ | Subcode | Context | Meaning | | --- | --- | --- | | none | messages | Generic invalid parameter; read `details` | | `33` | `GET /{MEDIA_ID}` | Unsupported object request: verify the ID, object type, deletion/expiry, and caller access | | `2388xxx` | `POST /message_templates` | A template content or format rule; each value maps to one rule such as header sample mismatch or invalid button configuration | | see linked guide | webhook subscription | For the exact "Before override the current callback uri" message, see [Webhook Error 100](https://dualhook.com/docs/whatsapp-error-100-webhook-override) | How to Prevent It[](https://dualhook.com/docs/whatsapp-error-100#how-to-prevent-it) ------------------------------------------------------------------------------------ * Validate `to` as E.164 digits before the request leaves your system. * Build message payloads from a typed schema per message type so a `template` object cannot end up on a `text` message. * Pin the Graph API version you develop against and re-check field names when you move versions. * Download inbound media promptly and treat a `403` or `100/33` on a media ID as final rather than retrying. Related Errors[](https://dualhook.com/docs/whatsapp-error-100#related-errors) ------------------------------------------------------------------------------ * [`/docs/whatsapp-error-131008`](https://dualhook.com/docs/whatsapp-error-131008) : a required parameter is missing entirely. * [`/docs/whatsapp-error-131009`](https://dualhook.com/docs/whatsapp-error-131009) : the parameter is present and named correctly but its value is not valid. * [`/docs/whatsapp-error-132000`](https://dualhook.com/docs/whatsapp-error-132000) : template variable count mismatch. * [`/docs/whatsapp-error-100-webhook-override`](https://dualhook.com/docs/whatsapp-error-100-webhook-override) : error 100 during webhook subscription. * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-100#faq) -------------------------------------------------------- **Is error 100 a permissions problem?** Usually it concerns a request parameter, but code 100 with an unsupported-object subcode can also mean the caller cannot access that object. Read the full error and verify the object ID and access. **Can I retry error 100?** First correct the parameter or resolve the object-access problem. Blindly retrying an unchanged request is unlikely to help. **Why do I get error 100 on a media download?** With subcode 33, verify the media ID, object type, expiry, and account access. Meta documents 7-day expiry for webhook media IDs and 30 days for uploaded media IDs; the code alone does not prove expiry. **Is error 100 the same as 131009?** They overlap. `100` is the general invalid-parameter code; `131009` is WhatsApp's more specific "parameter value is not valid" code. Read the error details when provided. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-100#how-dualhook-helps) -------------------------------------------------------------------------------------- Dualhook validates the request shape before forwarding it, so many malformed payloads are rejected with a named field in Dualhook's own `400` before they reach Meta; see [Runtime API Errors](https://dualhook.com/docs/runtime-api-errors#invalid-request) . When Meta returns `100`, Dualhook relays it unchanged with the `fbtrace_id`, including `error_data.details` when Meta provides it. Definitive media-access denials can be converted to a Dualhook 403; see [Media Rejected](https://dualhook.com/docs/runtime-api-errors#media-rejected) . If a connection keeps receiving error 100 on sends, the organization's admins receive a runtime health email pointing here. Sources: [Meta error codes](https://developers.facebook.com/documentation/business-messaging/whatsapp/support/error-codes/) , [Graph API error handling](https://developers.facebook.com/docs/graph-api/guides/error-handling/) , and [media ID expiry](https://developers.facebook.com/documentation/business-messaging/whatsapp/business-phone-numbers/media/) . Checked September 6, 2026. Related ------- * [WhatsApp Error 131009: Parameter Value Not Valid](https://dualhook.com/docs/whatsapp-error-131009) WhatsApp error 131009 means a parameter has the right name but a value Meta cannot use: check field formats, supported values, and object IDs. How to read details and fix it. * [WhatsApp Error 131008: Required Parameter Is Missing](https://dualhook.com/docs/whatsapp-error-131008) WhatsApp error 131008 means the request is missing a required parameter — common with flow buttons and dynamic template buttons. Causes and step-by-step fixes. * [WhatsApp Webhook Error 100: "Before Override the Current Callback URI…"](https://dualhook.com/docs/whatsapp-error-100-webhook-override) "(#100) Before override the current callback uri" means your app must subscribe to the WABA messages field first. The two-step fix, plus webhook 2200/2201. * [Runtime API Errors](https://dualhook.com/docs/runtime-api-errors) Troubleshoot Dualhook Runtime API failures by symptom: revoked keys, rejected media, wrong-connection scope, unsupported fields, validation, rate limits, and the send failure circuit. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp BSUID Contact Book and REQUEST_CONTACT_INFO | Dualhook Browse Docs BSUID Contact Book and Contact Requests ======================================= Meta contact book behavior for WhatsApp BSUIDs, phone-number inclusion rules, contact book deletion, REQUEST\_CONTACT\_INFO, and contacts webhooks. The Meta contact book is a portfolio-scoped feature that helps preserve phone-number continuity when WhatsApp users adopt usernames. What the Contact Book Does[](https://dualhook.com/docs/contacts-bsuid-contact-book#what-the-contact-book-does) --------------------------------------------------------------------------------------------------------------- When the contact book is enabled, Meta stores a user's phone number and BSUID for your business portfolio after qualifying interactions. Once stored, Meta can keep including that user's phone number in webhook payloads even if the user later adopts a username. Qualifying interactions include: * you send a message to the user's phone number, * you call the user's phone number, * you receive a message from the user's phone number, * you receive a call from the user's phone number. Only interactions after contact book rollout populate the contact book. Older interactions are not retroactively imported. Phone Number Inclusion[](https://dualhook.com/docs/contacts-bsuid-contact-book#phone-number-inclusion) ------------------------------------------------------------------------------------------------------- A user's phone number can appear in webhooks when: 1. the relevant business phone number sent a message or call to the user's phone number within the last 30 days, 2. the relevant business phone number received a message or call from the user's phone number within the last 30 days, 3. the user is in the portfolio-level contact book. The 30-day interaction checks are per business phone number. The contact book is scoped to the business portfolio, so any business phone number in that portfolio can benefit from a stored contact book entry. Retention and Disablement[](https://dualhook.com/docs/contacts-bsuid-contact-book#retention-and-disablement) ------------------------------------------------------------------------------------------------------------- Contact book data is retained until you disable the feature or deactivate the account. Meta's documentation says the contact book can be disabled in Meta Business Suite under Business settings > Business info. Disabling stops new storage and deletes existing contact book data. If you re-enable it later, previously deleted entries are not restored. If you have multiple business portfolios enrolled in the same parent BSUID account, contact books are still separate. Phone number and BSUID pairs must be recorded independently in each portfolio's contact book. Delete Contact Book Entry[](https://dualhook.com/docs/contacts-bsuid-contact-book#delete-contact-book-entry) ------------------------------------------------------------------------------------------------------------- Use the Contact Book API to delete a specific contact book entry: DELETE //contact_book?messaging_product=whatsapp&bsuid= Rules: * `bsuid` must be a regular BSUID in standard format, for example `US.13491208655302741918`. * The BSUID must belong to the same business portfolio as the business phone number. * Parent BSUIDs are not supported for contact book deletion. Response: { "messaging_product": "whatsapp", "success": true, "deleted": true } `deleted` is `true` when an entry existed and was deleted. It is `false` when no matching entry was found. REQUEST\_CONTACT\_INFO[](https://dualhook.com/docs/contacts-bsuid-contact-book#request_contact_info) ----------------------------------------------------------------------------------------------------- `REQUEST_CONTACT_INFO` lets you ask a WhatsApp user to share their phone number. It can be used in utility and marketing templates or as an interactive message. Template button component: { "type": "buttons", "buttons": [\ {\ "type": "REQUEST_CONTACT_INFO",\ "text": "Share Contact Info"\ }\ ] } The button does not require dynamic parameters when sending the template. Interactive message shape: { "messaging_product": "whatsapp", "recipient_type": "individual", "recipient": "", "type": "interactive", "interactive": { "type": "request_contact_info", "body": { "text": "Please share your contact information." }, "action": { "name": "request_contact_info" } } } Contacts Webhook[](https://dualhook.com/docs/contacts-bsuid-contact-book#contacts-webhook) ------------------------------------------------------------------------------------------- When a user shares contact information, the `messages` webhook carries a `contacts` message. New or important fields: | Field | Behavior | | --- | --- | | `contacts[].profile.username` | User username when adopted. | | `contacts[].user_id` | User BSUID. | | `messages[].from_user_id` | User BSUID. | | `messages[].contacts[].origin` | `contact_request` when shared through the button, `other` when shared directly in chat. | | `messages[].contacts[].vcard` | Included for direct contact sharing (`origin: "other"`). Omitted for `contact_request`. | | `messages[].contacts[].phones[]` | Shared phone-number data, including `phone`, `wa_id`, and type. | If contact book is enabled, Meta can add the shared phone number to your contact book. Local Storage Note[](https://dualhook.com/docs/contacts-bsuid-contact-book#local-storage-note) ----------------------------------------------------------------------------------------------- For businesses using Local Storage, Meta extracts the user's phone number from the shared contact card and stores that phone number in the Meta-hosted contact book. Only the phone number is extracted and stored; other vCard data is not retained beyond Meta's standard data-in-use period described in its docs. This means contact-request flows can still affect contact book behavior, but your own endpoint may also receive the full vCard in the webhook. Treat your endpoint and downstream logs as part of your privacy boundary. Dualhook Boundary[](https://dualhook.com/docs/contacts-bsuid-contact-book#dualhook-boundary) --------------------------------------------------------------------------------------------- Dualhook does not populate, delete, or manage Meta contact book entries. Contact book entries are created by interactions and contact-sharing events in your message flow, which is direct-routed from Meta to your endpoint via Webhook Override. Dualhook also does not store the contacts webhook payloads that contain shared phone numbers or vCards. Related ------- * [Meta BSUID, Usernames & Contact Book Transition Guide](https://dualhook.com/docs/contacts-bsuid) Overview of Meta's WhatsApp BSUID rollout: usernames, contact book behavior, parent BSUIDs, API recipient support, webhook changes, and migration timeline. * [BSUID Webhook Changes](https://dualhook.com/docs/contacts-bsuid-webhooks) WhatsApp webhook payload changes for BSUIDs, usernames, parent BSUIDs, optional phone-number fields, statuses, contacts, groups, calling, and Coexistence events. * [Contacts & Location Messages](https://dualhook.com/docs/contacts-location) Sending and receiving contact cards and location messages via Cloud API. * [Messaging Best Practices](https://dualhook.com/docs/messaging-best-practices) Opt-in, notification quality, contact validation, callback handling, and pre-launch checklist. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Marketing Conversion Tracking: Pixel, CAPI, SDK, fbclid | Dualhook Browse Docs Marketing Conversion Tracking ============================= Measure conversions from marketing messages with Meta Pixel, Conversions API, and the Meta SDK. Includes fbclid handling, the click-event webhook, and Android deep-link attribution. What Is Measured[](https://dualhook.com/docs/marketing-conversion-tracking#what-is-measured) --------------------------------------------------------------------------------------------- Conversion tracking is exclusive to [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) sends — it is not available on Cloud API marketing sends. Once a WABA is onboarded to MM API and your website or app has Pixel / Conversions API / Meta SDK in place, Meta automatically attributes downstream events back to the marketing message that drove the visit. **Web events** automatically attributed: * Add to cart, Initiate checkout, Purchase, Purchase value * Complete registration, Add to wishlist, Add payment info * Search, Lead, View content, Custom events **App events** automatically attributed: * App Purchase, App Purchase value, App Add to cart, App Initiate checkout * App Activations, App Complete registration, App Add to wishlist, App Add payment info * App Level achieved, App Rate, App Tutorial completion * App Search, App View content, App Other iOS app measurement is not currently supported. Android app measurement requires Meta Android SDK v17.0.2 or later. Prerequisites[](https://dualhook.com/docs/marketing-conversion-tracking#prerequisites) --------------------------------------------------------------------------------------- * WABA onboarded to MM API (see [MM API Onboarding](https://dualhook.com/docs/marketing-messages-onboarding) ) * For web conversions: Meta Pixel **or** Conversions API installed on the destination site * For app conversions: Meta Android SDK v17.0.2+, **or** Conversions API for App Events, **or** a supported Mobile Measurement Partner (MMP) * Marketing template messages sent through `/marketing_messages` (not `/messages`) If a business is not yet reporting events, follow Meta's [Pixel + Conversions API tutorial](https://developers.facebook.com/docs/meta-pixel/get-started) before relying on MM API conversion metrics. How Attribution Works[](https://dualhook.com/docs/marketing-conversion-tracking#how-attribution-works) ------------------------------------------------------------------------------------------------------- When a marketing template message includes a CTA URL button, Meta automatically appends a click ID (the `fbclid` query parameter) to that URL before sending. When the recipient taps the button: 1. The CTA URL with `fbclid` opens in the device's browser or app. 2. Pixel/Conversions API/Meta SDK reads `fbclid` from the URL and tags subsequent events with it. 3. Meta joins those events back to the originating marketing message and surfaces them in the Insights API and Ads Manager → Marketing Messages tab. If the user later converts via a different Meta surface (e.g. an Instagram ad), Meta uses **last-touch attribution** based on each surface's attribution window. Whichever surface had the most recent click before the conversion gets credit. The fbclid Click ID[](https://dualhook.com/docs/marketing-conversion-tracking#the-fbclid-click-id) --------------------------------------------------------------------------------------------------- `fbclid` is appended to your CTA URL automatically. Example: https://www.jaspersmarket.com/?fbclid=IwAR2F4-dbP0l7Mn1IawQQGCINEz7PYXQvwjNwB_qa2ofrHyiLjcbCRxTDMgk Three things must hold for attribution to work: 1. The recipient's tap loads the URL **with** `fbclid` intact. 2. Pixel / CAPI / SDK at the destination reads `fbclid` from that URL. 3. Subsequent conversion events are reported within the attribution window. If `fbclid` is dropped anywhere along the chain — by a redirect, a short-link service, an in-app browser, an Android intent handler — attribution silently fails and conversions are not credited to the marketing message. URL Compatibility (Short Links and Redirects)[](https://dualhook.com/docs/marketing-conversion-tracking#url-compatibility-short-links-and-redirects) ----------------------------------------------------------------------------------------------------------------------------------------------------- Short-link and URL-rewriting services frequently strip query parameters during redirects. A typical failure: 1. You configure CTA destination: `https://www.jaspersmarket.com/checkout?campaign=whatsapp_template` 2. Your link service rewrites it to: `https://www.example.com/jaspersmarket` 3. Meta appends fbclid: `https://www.example.com/jaspersmarket?fbclid=xyz789` 4. The link service redirects to your site but **drops the query string**: `https://www.jaspersmarket.com/checkout?campaign=whatsapp_template` 5. `fbclid=xyz789` is gone — conversion goes unattributed. **Recommendations:** * If you use short links or URL rewriting, validate end-to-end that `fbclid` survives every hop. * Avoid appending custom parameters _after_ `fbclid` in ways that could disrupt URL parsing. * Test the full URL with all parameters in your real client before going live. * Validate conversion reporting in Ads Manager / Insights API before scaling sends. Web Conversions: Pixel and Conversions API[](https://dualhook.com/docs/marketing-conversion-tracking#web-conversions-pixel-and-conversions-api) ------------------------------------------------------------------------------------------------------------------------------------------------ If your site already reports events via Meta Pixel or Conversions API for web, no additional MM API integration is needed. The same Pixel/CAPI events are automatically credited to MM API marketing messages when the visitor arrived via an `fbclid`\-tagged CTA. If you have not yet set up event reporting, the Meta Pixel + Conversions API tutorial is the entry point. Both methods can run side-by-side; Conversions API is the more reliable signal because it bypasses browser-side tracking blockers. App Conversions: Meta SDK and Conversions API[](https://dualhook.com/docs/marketing-conversion-tracking#app-conversions-meta-sdk-and-conversions-api) ------------------------------------------------------------------------------------------------------------------------------------------------------ Three integration options: **1\. Meta SDK (Android)** * Upgrade to Meta Android SDK v17.0.2 or later. * The SDK reads `fbclid` and other attribution params from the deep link automatically. * iOS not currently supported for MM API app measurement. **2\. Conversions API for App Events** * Send `campaign_ids` parsed from the `al_applink_data` query parameter on the deep link. * The deep link Meta sends carries an `al_applink_data` JSON blob containing `campaign_ids`, `ad_id`, `adgroup_id`, etc. Parse those and forward them on event submissions. * Set up Pixel/Conversions API for the website fallback URL too — users without the app installed land there. **3\. Mobile Measurement Partner (MMP)** * Some MMPs auto-forward app conversion events to Meta. Check with your provider — if supported, no integration work is needed on your side. Android Deep Link Attribution[](https://dualhook.com/docs/marketing-conversion-tracking#android-deep-link-attribution) ----------------------------------------------------------------------------------------------------------------------- Android routes deep links via intent filters. A deep link URL has three parts: scheme (`https`, `myapp`), path (`/product/123`), and query parameters (`?fbclid=...&campaignId=...`). When the user taps a deep link from a WhatsApp message: 1. Android finds an app with a matching intent filter. 2. Creates an implicit Intent. 3. Delivers it to the target Activity (often via `onCreate()` and/or `onNewIntent()`). 4. **Your app must explicitly read and persist the URL's query parameters.** If you don't, they are effectively lost after the first screen. **Implementation checklist:** * Read the full URI from the incoming Intent, not just the path. * Handle both cold start (`onCreate()`) and warm start / re-use (`onNewIntent()`) — the user may launch the app fresh, or Android may reuse a backgrounded Activity. * Extract attribution params (at minimum `fbclid`, plus any others you depend on). * Persist them somewhere durable (SharedPreferences, local DB) with a timestamp so they survive across sessions for deferred attribution. * Refresh the stored value when a newer one arrives — these IDs change session-to-session. * **Do not drop query parameters on internal redirects.** If you convert the inbound URI to an internal route, carry attribution params forward or persist them before routing. **Test with adb:** adb shell am start -W -a android.intent.action.VIEW \ -d "myapp://some/path?fbclid=TEST123&campaignId=TESTCAMPAIGN" Then confirm in your logs that the app received `fbclid` and persisted it. Click Event Webhook[](https://dualhook.com/docs/marketing-conversion-tracking#click-event-webhook) --------------------------------------------------------------------------------------------------- In addition to attribution, MM API can deliver a webhook payload when users click on the body or CTA of a marketing message. Subscribe to the `messages` field on the `whatsapp_business_account` webhook topic. Limitations: * Currently not available to all users (rolled out in waves). * Click events are only available for messages sent in the last 7 days. { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "value": {\ "messaging_product": "whatsapp",\ "metadata": {\ "display_phone_number": "15550001234",\ "phone_number_id": "123456789012345"\ },\ "user_actions": [\ {\ "action_type": "marketing_messages_link_click",\ "timestamp": "",\ "marketing_messages_link_click_data": {\ "click_component": "cta",\ "product_id": "",\ "click_id": "",\ "tracking_token": ""\ }\ }\ ]\ },\ "field": "messages"\ }\ ]\ }\ ] } | Field | Notes | | --- | --- | | `action_type` | Always `marketing_messages_link_click` | | `click_component` | `cta` or `body` | | `click_id` | Unique identifier for the click — also appended to the destination URL | | `tracking_token` | Internal Meta token, opaque | | `product_id` | Set if assigned in Ads Manager or Marketing API | This webhook arrives on the `messages` field, so in Dualhook setups it is delivered directly to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) — Dualhook does not ingest it. Viewing Metrics[](https://dualhook.com/docs/marketing-conversion-tracking#viewing-metrics) ------------------------------------------------------------------------------------------- Two reporting surfaces: | API | Strengths | Permission | | --- | --- | --- | | **Insights API** (Ads Manager) | Conversion metrics (web + app), spend, cost-per-click, cost-per-delivery, benchmarks | `ads_read` | | **Conversation Analytics** (Business Management API) | Sent / delivered / read / clicked / cost — basic metrics, no conversions | `whatsapp_business_management` | To include MM API messages in Conversation Analytics, query with `conversation_categories=MARKETING_MESSAGES` or `MARKETING_LITE`: GET /?fields=conversation_analytics.start().end().granularity(DAILY).conversation_categories(MARKETING_LITE).dimensions(["CONVERSATION_CATEGORY"]) If `conversation_categories` is omitted, only Cloud API conversation metrics are returned — MM API sends are excluded. For richer metrics including conversions, prefer the Insights API. Meta recommends partners surface Insights API metrics in their own dashboards rather than only relying on Conversation Analytics. Template-to-Ad Sync Caveats[](https://dualhook.com/docs/marketing-conversion-tracking#template-to-ad-sync-caveats) ------------------------------------------------------------------------------------------------------------------- When a marketing template is created or first used after 7+ days of dormancy, Meta needs **up to 10 minutes** to sync it to its corresponding ad account before optimization and measurement work properly: * **New templates:** Wait ~10 minutes after approval before sending production traffic. * **Reactivated templates** (unused 7+ days): Send one warm-up message, then wait 10 minutes before scaling. * **CTA URL changes:** Campaign and Message Set parameters sync only **once per template**. Editing the CTA URL on an existing template will **not** re-link conversion measurement to the new URL — you must create a new template. * **Templates that existed before MM API onboarding:** No conversion metrics. Create new templates to enable conversions. Related ------- * [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) Reference for Meta's separate /marketing\_messages product, which is not exposed by Dualhook's runtime or Embedded Signup configuration. * [Marketing Deep Links](https://dualhook.com/docs/marketing-deep-links) Map a URL button on a marketing template to an Android app deep link via app\_deep\_link, with fallback URLs for non-Android clients and uninstalled-app users. * [Marketing Messages](https://dualhook.com/docs/marketing-messages) Marketing template messaging through Dualhook's standard Cloud API runtime, plus boundaries for Meta's separate MM API product. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Management Webhooks: Template, Quality and Account Events | Dualhook Browse Docs Webhook Events & Notifications ============================== Management webhook events: template status, quality updates, account alerts, and forwarding. Not every WhatsApp webhook field can be routed directly to your server. This page covers the management events that stay on Dualhook's app-level callback, such as template status and category changes, phone-number quality updates, and account alerts, and how Dualhook forwards them to you with retry visibility. For the message-path fields Meta delivers straight to your endpoint, see [Webhook Override](https://dualhook.com/docs/webhook-override) . Delivery Model[](https://dualhook.com/docs/webhook-events#delivery-model) -------------------------------------------------------------------------- Dualhook configures webhook subscriptions and override at the [WABA](https://dualhook.com/docs/whatsapp-business-accounts) level, then Meta chooses the callback target for each webhook field: * **Override-capable fields** such as `messages`, `history`, `smb_message_echoes`, and `smb_app_state_sync` are delivered directly from Meta to your webhook endpoint when override is active. * **Non-overridable management fields** are received on Dualhook's app-level callback for operational handling, then forwarded to your configured webhook endpoint where applicable. With an active override, these inbound payloads are not sent to Dualhook. Dualhook does not proxy or store their message bodies, media, `history`, or `smb_app_state_sync` contact payloads. Supported outbound requests use the separate no-retention runtime. Dualhook stores and processes operational metadata for monitoring and dashboards. For Coexistence onboarding, Dualhook triggers Meta's one-time `history` and `smb_app_state_sync` sync requests after the override is accepted. Meta then delivers any resulting payloads directly to your endpoint. A successful trigger means Meta accepted the request, not that history was shared or delivered. With Meta's early April 2026 BSUID rollout and later username launch, message webhooks include new BSUID fields (`user_id`, `from_user_id`) and some existing fields (`wa_id`, `from`) may be omitted. See the [BSUID webhook changes reference](https://dualhook.com/docs/contacts-bsuid-webhooks) for the full list of field changes. Managed Event Fields[](https://dualhook.com/docs/webhook-events#managed-event-fields) -------------------------------------------------------------------------------------- Dualhook tracks these management fields: * [`message_template_status_update`](https://dualhook.com/docs/webhook-message-template-status-update) * [`message_template_quality_update`](https://dualhook.com/docs/webhook-message-template-quality-update) * `phone_number_quality_update` * `phone_number_name_update` * [`account_update`](https://dualhook.com/docs/webhook-account-update) * [`account_review_update`](https://dualhook.com/docs/webhook-account-review-update) * [`business_capability_update`](https://dualhook.com/docs/webhook-business-capability-update) * [`template_category_update`](https://dualhook.com/docs/webhook-template-category-update) * [`security`](https://dualhook.com/docs/webhook-security) * [`account_alerts`](https://dualhook.com/docs/webhook-account-alerts) `smb_app_state_sync` is **not a Dualhook-managed event**. In a standard Dualhook setup for WhatsApp Coexistence, Meta routes it directly to your endpoint via Webhook Override. Message-Path vs Management Events[](https://dualhook.com/docs/webhook-events#message-path-vs-management-events) ---------------------------------------------------------------------------------------------------------------- | Field | Delivery path | Dualhook behavior | | --- | --- | --- | | `messages` | Meta → your endpoint (override) | Not sent to Dualhook when override is active. Fallback deliveries to Dualhook's app callback are acknowledged without processing. | | [`smb_message_echoes`](https://dualhook.com/docs/webhook-smb-message-echoes) | Meta → your endpoint (override) | Not sent to Dualhook when override is active. Fallback deliveries are acknowledged without processing. | | [`history`](https://dualhook.com/docs/webhook-history) | Meta → your endpoint (override) | One-time sync requested by Dualhook after onboarding; payload delivery is Meta → your endpoint. Fallback deliveries are acknowledged without processing. | | [`smb_app_state_sync`](https://dualhook.com/docs/webhook-smb-app-state-sync) | Meta → your endpoint (override) | One-time contact sync requested by Dualhook after onboarding; payload delivery is Meta → your endpoint. Fallback deliveries are acknowledged without processing. | | Management fields | App callback → Dualhook → your endpoint | Metadata handled for state updates + forwarded | Template Status Update[](https://dualhook.com/docs/webhook-events#template-status-update) ------------------------------------------------------------------------------------------ { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "message_template_status_update",\ "value": {\ "message_template_id": "123456789012345",\ "message_template_name": "order_update_v1",\ "message_template_language": "en_US",\ "event": "APPROVED"\ }\ }\ ]\ }\ ] } Template Category Reclassification[](https://dualhook.com/docs/webhook-events#template-category-reclassification) ------------------------------------------------------------------------------------------------------------------ { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "template_category_update",\ "value": {\ "message_template_id": "123456789012345",\ "message_template_name": "promo_like_utility",\ "old_category": "UTILITY",\ "new_category": "MARKETING"\ }\ }\ ]\ }\ ] } Phone Number Quality Update[](https://dualhook.com/docs/webhook-events#phone-number-quality-update) ---------------------------------------------------------------------------------------------------- { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "phone_number_quality_update",\ "value": {\ "event": "QUALITY_UPDATED",\ "phone_number_id": "123456789012345",\ "display_phone_number": "15550001234",\ "current_limit": "TIER_10K",\ "current_quality_rating": "YELLOW",\ "previous_quality_rating": "GREEN"\ }\ }\ ]\ }\ ] } Account Update Events[](https://dualhook.com/docs/webhook-events#account-update-events) ---------------------------------------------------------------------------------------- The `account_update` field carries several pricing- and location-related sub-events. They share the same wrapper but differ in the `event` value and inner shape. ### Volume-Based Pricing Tier Update[](https://dualhook.com/docs/webhook-events#volume-based-pricing-tier-update) Sent when the portfolio reaches a new [volume tier](https://dualhook.com/docs/pricing#volume-tiers) for a given market–category. Available since October 1, 2025. { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "time": 1743451903,\ "changes": [\ {\ "field": "account_update",\ "value": {\ "event": "VOLUME_BASED_PRICING_TIER_UPDATE",\ "volume_tier_info": {\ "tier_update_time": 1743451903,\ "pricing_category": "UTILITY",\ "tier": "25000001:50000000",\ "effective_month": "2025-11",\ "region": "India"\ }\ }\ }\ ]\ }\ ] } Multiple webhooks can describe the same tier switch. Treat the one with the smallest `tier_update_time` as canonical. ### Authentication-International Eligibility Update[](https://dualhook.com/docs/webhook-events#authentication-international-eligibility-update) Sent when the business is deemed eligible for [authentication-international rates](https://dualhook.com/docs/pricing#authentication-international-rates) , with at least 30 days notice before billing starts. { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "time": 1732057507,\ "changes": [\ {\ "field": "account_update",\ "value": {\ "event": "AUTH_INTL_PRICE_ELIGIBILITY_UPDATE",\ "auth_international_rate_eligibility": {\ "start_time": 1734649507,\ "exception_countries": [\ { "country_code": "ID", "start_time": 1742450707 }\ ]\ }\ }\ }\ ]\ }\ ] } ### Primary Business Location Update[](https://dualhook.com/docs/webhook-events#primary-business-location-update) Sent when Meta sets or updates the business's primary location (used to determine whether authentication-international rates apply). { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "time": 1732057507,\ "changes": [\ {\ "field": "account_update",\ "value": {\ "event": "BUSINESS_PRIMARY_LOCATION_COUNTRY_UPDATE",\ "country": "BE"\ }\ }\ ]\ }\ ] } `country` is an ISO 3166-1 alpha-2 country code. Account Alert[](https://dualhook.com/docs/webhook-events#account-alert) ------------------------------------------------------------------------ { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "account_alerts",\ "value": {\ "alert_type": "POLICY_ENFORCEMENT",\ "alert_severity": "HIGH",\ "alert_description": "Recent activity may violate messaging policy."\ }\ }\ ]\ }\ ] } Forwarding Headers[](https://dualhook.com/docs/webhook-events#forwarding-headers) ---------------------------------------------------------------------------------- When Dualhook forwards management events to your endpoint, it adds: * `X-Dualhook-Event` * `X-Dualhook-WABA` * `X-Dualhook-Connection` * `X-Dualhook-Trace` Reliability Behavior[](https://dualhook.com/docs/webhook-events#reliability-behavior) -------------------------------------------------------------------------------------- * Event forwarding timeout: 15 seconds. * Delivery status is logged (`delivered`, `failed`, `pending`). * Duplicate management events within a short window are deduplicated using event metadata. Related ------- * [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) Reference list of all 22+ WhatsApp Business Platform webhook subscription fields, what each notifies you of, required permissions, payload size limits, retry policy, mTLS support, and IP allowlisting. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Authentication Bulk Management | Dualhook Browse Docs Authentication Bulk Management ============================== Use upsert\_message\_templates to create or update authentication templates across many languages in one call, and use message\_template\_previews to preview localized text before committing. What Bulk Management Does[](https://dualhook.com/docs/authentication-bulk-management#what-bulk-management-does) ---------------------------------------------------------------------------------------------------------------- Two endpoints help when you need authentication templates in many languages or want to preview the localized text before creating templates: | Endpoint | Purpose | | --- | --- | | `POST //upsert_message_templates` | Create or update authentication templates across multiple languages in a single call | | `GET //message_template_previews` | Generate preview body, footer, and button text in any combination of languages and configuration options — without creating a template | Both are specific to authentication templates (the preset-text structure makes localized previews tractable). Other categories don't have an upsert endpoint or a preview API. When to Use Each[](https://dualhook.com/docs/authentication-bulk-management#when-to-use-each) ---------------------------------------------------------------------------------------------- * Use **previews** before deciding which language/security/expiration combinations to ship — see exactly what the user will see in each language. * Use **upsert** when you've decided on a configuration and want to roll it out across N languages in one transactional API call rather than N create/update calls. Especially useful when adding or refreshing language coverage. Upsert Message Templates[](https://dualhook.com/docs/authentication-bulk-management#upsert-message-templates) -------------------------------------------------------------------------------------------------------------- `POST //upsert_message_templates` creates a template if one with that `name`+`language` does not exist, or updates the existing one if it does. The same call can target multiple languages — Meta returns a per-language status array. If you mix create and update in one call (some languages already exist, others don't), Meta handles each independently and returns the per-language outcome. Upsert Payload Shape[](https://dualhook.com/docs/authentication-bulk-management#upsert-payload-shape) ------------------------------------------------------------------------------------------------------ { "name": "", "languages": ["en_US", "es_ES", "fr"], "category": "AUTHENTICATION", "components": [\ {\ "type": "BODY",\ "add_security_recommendation": true\ },\ {\ "type": "FOOTER",\ "code_expiration_minutes": 10\ },\ {\ "type": "BUTTONS",\ "buttons": [\ {\ "type": "OTP",\ "otp_type": "",\ "supported_apps": [\ {\ "package_name": "",\ "signature_hash": ""\ }\ ]\ }\ ]\ }\ ] } `languages` is the multi-language replacement for the singular `language` field used in single-template create. Property Differences vs Single Create[](https://dualhook.com/docs/authentication-bulk-management#property-differences-vs-single-create) ---------------------------------------------------------------------------------------------------------------------------------------- All standard authentication template create properties are supported, with three exceptions: | Property | Single create | Upsert | | --- | --- | --- | | `language` | Required string | **Not supported** — use `languages` array instead | | `text` (button label) | Optional | **Not supported** — labels fall back to localized defaults | | `autofill_text` | Optional | **Not supported** — falls back to localized default | If you need custom button labels, use single-create per language instead. Example: Bulk Copy-Code in 3 Languages[](https://dualhook.com/docs/authentication-bulk-management#example-bulk-copy-code-in-3-languages) ----------------------------------------------------------------------------------------------------------------------------------------- curl 'https://graph.facebook.com///upsert_message_templates' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "authentication_code_copy_code_button", "languages": ["en_US", "es_ES", "fr"], "category": "AUTHENTICATION", "components": [\ { "type": "BODY", "add_security_recommendation": true },\ { "type": "FOOTER", "code_expiration_minutes": 10 },\ {\ "type": "BUTTONS",\ "buttons": [\ { "type": "OTP", "otp_type": "COPY_CODE" }\ ]\ }\ ] }' Response — one entry per language, each with its own template id and review status: { "data": [\ { "id": "954638012257287", "status": "APPROVED", "language": "en_US" },\ { "id": "969725527415202", "status": "APPROVED", "language": "es_ES" },\ { "id": "969725530748535", "status": "APPROVED", "language": "fr" }\ ] } Authentication templates often auto-approve (no manual review needed) when the structure matches the preset format and the security/expiration values are within range — that's why this example shows `APPROVED` immediately. For other categories or non-trivial templates, expect `PENDING`. Example: Bulk One-Tap with Update[](https://dualhook.com/docs/authentication-bulk-management#example-bulk-one-tap-with-update) ------------------------------------------------------------------------------------------------------------------------------- This call updates the existing `en_US` template (if any) and creates new `es_ES` and `fr` templates with one-tap autofill buttons: curl 'https://graph.facebook.com///upsert_message_templates' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "authentication_code_autofill_button", "languages": ["en_US", "es_ES", "fr"], "category": "AUTHENTICATION", "components": [\ { "type": "BODY", "add_security_recommendation": true },\ { "type": "FOOTER", "code_expiration_minutes": 15 },\ {\ "type": "BUTTONS",\ "buttons": [\ {\ "type": "OTP",\ "otp_type": "ONE_TAP",\ "supported_apps": [\ {\ "package_name": "com.example.luckyshrub",\ "signature_hash": "K8a/AINcGX7"\ }\ ]\ }\ ]\ }\ ] }' For one-tap and zero-tap, see [OTP Handshake & Android SDK](https://dualhook.com/docs/authentication-otp-handshake) for the runtime side. Message Template Previews[](https://dualhook.com/docs/authentication-bulk-management#message-template-previews) ---------------------------------------------------------------------------------------------------------------- `GET //message_template_previews` returns the actual body, footer, and button text in your requested languages — useful for previewing what your users will see before committing to a template configuration. Query parameters: | Parameter | Required? | Notes | | --- | --- | --- | | `category` | Yes | Must be `AUTHENTICATION` | | `languages` | Optional | Comma-separated list (e.g. `en_US,es_ES`). Omit to get all supported languages. | | `add_security_recommendation` | Optional | `true` to include "For your security…" string. Omit or `false` to exclude. | | `code_expiration_minutes` | Optional | Integer 1–90. If included, footer string with this number is returned. Omit to exclude footer. | | `button_types` | Required | Comma-separated. For authentication this must be `OTP`. | Example: Preview en\_US + es\_ES with Security and Expiration[](https://dualhook.com/docs/authentication-bulk-management#example-preview-en_us--es_es-with-security-and-expiration) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ curl 'https://graph.facebook.com///message_template_previews?category=AUTHENTICATION&languages=en_US,es_ES&add_security_recommendation=true&code_expiration_minutes=10&button_types=OTP' \ -H 'Authorization: Bearer ' Response: { "data": [\ {\ "body": "*{{1}}* is your verification code. For your security, do not share this code.",\ "buttons": [\ { "autofill_text": "Autofill", "text": "Copy code" }\ ],\ "footer": "This code expires in 10 minutes.",\ "language": "en_US"\ },\ {\ "body": "Tu código de verificación es *{{1}}*. Por tu seguridad, no lo compartas.",\ "buttons": [\ { "autofill_text": "Autocompletar", "text": "Copiar código" }\ ],\ "footer": "Este código caduca en 10 minutos.",\ "language": "es_ES"\ }\ ] } The preview is exactly what would render if you created the template with the same configuration. Use it for design reviews, customer support copy, or to validate that a language code is supported before adding it to your `upsert_message_templates` call. Related ------- * [Authentication Messages](https://dualhook.com/docs/authentication-messages) OTP and verification code delivery via WhatsApp: copy-code, one-tap, and zero-tap templates, linked device security, the 'I didn't request a code' webhook, and PendingIntent deprecation. * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Marketing Messages API for WhatsApp: Endpoint, Features, Webhooks | Dualhook Browse Docs Marketing Messages API for WhatsApp =================================== Reference for Meta's separate /marketing\_messages product, which is not exposed by Dualhook's runtime or Embedded Signup configuration. What MM API for WhatsApp Is[](https://dualhook.com/docs/marketing-messages-api#what-mm-api-for-whatsapp-is) ------------------------------------------------------------------------------------------------------------ > **Not exposed by Dualhook:** Dualhook's Embedded Signup v4 configuration does not request Marketing Messages Lite and the allowlisted runtime does not expose `/marketing_messages`. All examples on this page describe Meta's separate product and require a separately reviewed, customer-owned Meta app and authorization. Never substitute a `dh_live_...` key or a Dualhook-managed credential. **Marketing Messages API for WhatsApp** (formerly "Marketing Messages Lite API", now generally available) is Meta's optimized send path for marketing template messages. It uses the same templates, the same phone numbers, and the same per-message billing as Cloud API — but routes sends through a parallel `/marketing_messages` endpoint that adds delivery and creative optimizations. MM API is **send-only**. To receive incoming messages, status webhooks for non-marketing traffic, or send anything other than marketing templates, you continue to use Cloud API in parallel on the same phone number. Feature Comparison vs Cloud API[](https://dualhook.com/docs/marketing-messages-api#feature-comparison-vs-cloud-api) -------------------------------------------------------------------------------------------------------------------- | Capability | Cloud API | MM API for WhatsApp | | --- | --- | --- | | Send marketing templates | Yes | Yes | | Send authentication / utility / service templates | Yes | No | | Send freeform (`text`, `image`, etc.) | Yes (inside CSW) | No | | Receive incoming messages and statuses | Yes | No | | Quality-based delivery optimization | No | Yes — up to ~9% higher delivery for high-engagement messages | | [Automatic creative optimizations](https://dualhook.com/docs/marketing-creative-optimizations) | No | Yes | | Animated image (GIF) header | No | Yes | | [Android app deep links on URL buttons](https://dualhook.com/docs/marketing-deep-links) | No | Yes | | Customizable TTL on marketing templates | No | Yes (12 hours – 30 days) | | TTL on auth/utility templates | Yes | N/A | | Performance benchmarks vs similar businesses | No | Yes | | Tailored creative recommendations | No | Yes | | [Conversion metrics (Web/App)](https://dualhook.com/docs/marketing-conversion-tracking) | No | Yes | | Cost metrics (spend per template, per click, per delivery) | Yes | Yes | | Basic metrics (sent, delivered, read, clicked, errors) | Yes | Yes | | Local Storage support | Yes | Yes | | Formal compliance program support | Yes | Yes | | [Max-price (beta)](https://dualhook.com/docs/marketing-max-price) | No | Yes | | Onboarding | Embedded Signup | Embedded Signup, Intent API, Intent UI | The /marketing\_messages Endpoint[](https://dualhook.com/docs/marketing-messages-api#the-marketing_messages-endpoint) ---------------------------------------------------------------------------------------------------------------------- POST https://graph.facebook.com///marketing_messages The send payload is identical to Cloud API's `/messages` payload for template messages, with two MM-specific optional fields: { "messaging_product": "whatsapp", "recipient_type": "individual", "to": "", "type": "template", "template": { "name": "", "language": { "code": "" }, "components": [ /* same shape as Cloud API */ ] }, "product_policy": "CLOUD_API_FALLBACK", "message_activity_sharing": true } | Field | Default | Purpose | | --- | --- | --- | | `product_policy` | `CLOUD_API_FALLBACK` | If the WABA hasn't completed MM API onboarding, the message is automatically sent through Cloud API instead. Set to `STRICT` to disable this fallback (the call fails instead of falling back). | | `message_activity_sharing` | WABA-level default | Per-message override of whether read/click events from this send are shared with Meta to improve optimization. Boolean. | Authentication must come from the business's own separately reviewed Meta integration. Dualhook does not reveal or authorize use of its customer-scoped BISU token for this endpoint. Fallback Behavior to Cloud API[](https://dualhook.com/docs/marketing-messages-api#fallback-behavior-to-cloud-api) ------------------------------------------------------------------------------------------------------------------ By default (`product_policy` omitted or set to `CLOUD_API_FALLBACK`), MM API will silently route a marketing template send through Cloud API if the WABA has not yet completed MM API onboarding. This makes MM API safe to wire up _before_ the customer has signed the ToS. Two important caveats: * If you have set `disable_marketing_messages_on_cloud_api: true` on the WABA (see below), the fallback is rejected with error `131063` because Cloud API marketing is disabled. * If you set `product_policy: STRICT` on the call, no fallback is attempted regardless of the disable flag. Disable Marketing on Cloud API[](https://dualhook.com/docs/marketing-messages-api#disable-marketing-on-cloud-api) ------------------------------------------------------------------------------------------------------------------ Once a WABA has fully onboarded to MM API, you can require all marketing traffic to go through MM API by setting: POST / { "disable_marketing_messages_on_cloud_api": true } When this is `true`, attempting to send a `MARKETING` category template through `/messages` returns: { "error": { "code": 131063, "message": "(#131063) Marketing templates disabled for Cloud API", "error_data": { "details": "Your template is categorized as Marketing, but marketing templates are currently disabled for your Cloud API configuration. To send this template, use the Marketing Messages API for WhatsApp or enable marketing templates on Cloud API by turning off disable_marketing_messages_on_cloud_api." } } } This setting only takes effect on WABAs that have **fully onboarded** (ToS signed). Setting it on a partially-onboarded WABA can produce blocked sends without a working fallback path. To check the current value: GET /?fields=disable_marketing_messages_on_cloud_api To re-enable Cloud API marketing, set the field back to `false`. Geographic Availability[](https://dualhook.com/docs/marketing-messages-api#geographic-availability) ---------------------------------------------------------------------------------------------------- | Region | MM API behavior | | --- | --- | | EEA, UK, Japan, South Korea | Sends work, but **no delivery optimizations**, **no click/conversion reporting**, **no per-user marketing limits**. Metrics available only via Business Management API and WhatsApp Manager (not Ads Manager / Insights API). | | United States | Marketing messages to US recipients are blocked by Meta with error `131049` since April 1, 2025 — applies to **both** Cloud API and MM API. US-based business numbers can still send to non-US recipients. | | Russia | MM API works since June 20, 2025, but with no delivery optimization or click/conversion reporting for sends from Russian business profiles or Russian payment methods. | | Cuba, Iran, North Korea, Syria, Crimea, Donetsk, Luhansk | Cannot onboard, cannot send. Applies platform-wide. | | Everywhere else | Full feature set. | Status Webhooks[](https://dualhook.com/docs/marketing-messages-api#status-webhooks) ------------------------------------------------------------------------------------ MM API sends emit the same `sent` / `delivered` / `read` status webhooks as Cloud API, **but the pricing object distinguishes them**: "conversation": { "id": "", "origin": { "type": "marketing_lite" } }, "pricing": { "billable": true, "pricing_model": "PMP", "category": "marketing_lite" } For Cloud API marketing sends, `pricing.category` is `marketing` and `conversation.origin.type` is `marketing`. For MM API sends, both fields use `marketing_lite`. If you want to attribute outgoing message IDs back to their send path (e.g. for billing reconciliation), log which endpoint each `wamid` was returned from at send time and join against the webhook's `id` field. Incoming Messages (Send-Only)[](https://dualhook.com/docs/marketing-messages-api#incoming-messages-send-only) -------------------------------------------------------------------------------------------------------------- MM API does not deliver incoming messages. The `messages` field in webhooks for inbound user messages always comes through Cloud API's webhook subscription. In Dualhook setups, this is delivered directly to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) — there is no separate MM API inbound channel to subscribe to. Errors Specific to MM API[](https://dualhook.com/docs/marketing-messages-api#errors-specific-to-mm-api) -------------------------------------------------------------------------------------------------------- | Code | Meaning | Fix | | --- | --- | --- | | `131049` | Marketing messages cannot be delivered to US recipients | Suppress US `wa_id`s before sending | | `131063` | Marketing templates disabled for Cloud API | Either send via `/marketing_messages` or set `disable_marketing_messages_on_cloud_api: false` | | `131061` | Templates with `bid_spec` (max-price) cannot be sent via Cloud API | Send via `/marketing_messages` instead | | `131050` | Recipient stopped marketing messages from your business | Suppress the recipient and respect the `user_preferences` webhook | Dualhook's Stance[](https://dualhook.com/docs/marketing-messages-api#dualhooks-stance) --------------------------------------------------------------------------------------- Dualhook supports marketing templates through the standard Cloud API `/messages` route at `api.dualhook.com`. It does not request MM Lite terms, ad assets, `ads_read`, or the MM Lite feature during Embedded Signup. Meta-generated `MM_LITE_TERMS_SIGNED` management events may still be observed and recorded as lifecycle metadata, but Dualhook does not initiate that event or enable the product. Related ------- * [Marketing Messages](https://dualhook.com/docs/marketing-messages) Marketing template messaging through Dualhook's standard Cloud API runtime, plus boundaries for Meta's separate MM API product. * [MM API Onboarding](https://dualhook.com/docs/marketing-messages-onboarding) Reference for MM API onboarding outside Dualhook's managed configuration. Dualhook does not request Marketing Messages Lite. * [Marketing Conversion Tracking](https://dualhook.com/docs/marketing-conversion-tracking) Measure conversions from marketing messages with Meta Pixel, Conversions API, and the Meta SDK. Includes fbclid handling, the click-event webhook, and Android deep-link attribution. * [Marketing Creative Optimizations](https://dualhook.com/docs/marketing-creative-optimizations) Configure Meta's automatic creative optimizations on MM API marketing templates: creative\_features\_spec at template and WABA level, opt-in/opt-out per key. * [Marketing Max-Price (Beta)](https://dualhook.com/docs/marketing-max-price) Set a maximum price per marketing message delivery on MM API for WhatsApp using bid\_spec. Includes per-message multiplier, reach estimation, and rollout phases. * [Pricing](https://dualhook.com/docs/pricing) Dualhook subscription plans, what is included, and how pricing relates to Meta messaging fees. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Business Usernames: API, Rules, Webhook and Errors | Dualhook Browse Docs WhatsApp Business Usernames =========================== Business username rules for WhatsApp business phone numbers, including format, display priority, reserved usernames, management APIs, webhook updates, and errors. WhatsApp business usernames are searchable identifiers for business phone numbers. They are related to the BSUID rollout, but they are not a privacy feature for businesses. Business vs User Usernames[](https://dualhook.com/docs/whatsapp-business-usernames#business-vs-user-usernames) --------------------------------------------------------------------------------------------------------------- User usernames let WhatsApp users show a username instead of a phone number. Business usernames let users search for and reach a business by username. Key differences: | Area | User username | Business username | | --- | --- | --- | | Privacy effect | Can hide the user's phone number in the app and in some payloads. | Does not hide the business phone number. | | Mapping | One username per WhatsApp user. | One username per business phone number. | | Searchability | User-focused identity. | Users can search the exact business username to reach the business. | | API impact | Drives BSUID and optional `wa_id` behavior. | Has username management APIs and status webhook. | Business usernames are unique across WhatsApp. A business phone number can have only one username at a time, and no two phone numbers can use the same username. Display Priority[](https://dualhook.com/docs/whatsapp-business-usernames#display-priority) ------------------------------------------------------------------------------------------- Meta documents this chat-window display priority, from highest to lowest: 1. Saved contact name 2. Verified business name or Official Business Account name 3. Username 4. Phone number Your business phone number still appears in the business profile. Format Rules[](https://dualhook.com/docs/whatsapp-business-usernames#format-rules) ----------------------------------------------------------------------------------- Business usernames: * may contain English letters, digits, periods, and underscores; * do not support non-English characters; * must be 3 to 35 characters; * must contain at least one English letter; * must not start or end with a period; * must not contain two consecutive periods; * must not start with `www`; * must not end with a domain-like suffix such as `.com`, `.org`, `.net`, `.edu`, `.gov`, `.mil`, `.us`, `.in`, or `.html`. Case is ignored for comparison. Periods and underscores are significant, so `myid`, `my.id`, and `my_id` are distinct. Reserved Usernames[](https://dualhook.com/docs/whatsapp-business-usernames#reserved-usernames) ----------------------------------------------------------------------------------------------- Before broad username availability, Meta may reserve usernames for a business. You can claim a reserved username through WhatsApp Manager, Meta Business Suite, or the API. If the reserved username is already used by your Facebook Page or Instagram account, the business phone number must be linked to that Page or account before claiming. The person linking it needs sufficient control or `manage_phone` permission. Adopt or Change Username[](https://dualhook.com/docs/whatsapp-business-usernames#adopt-or-change-username) ----------------------------------------------------------------------------------------------------------- Meta's username API lets you adopt or change a business username: POST //username { "username": "", "transfer_action": "none" } `username` is required. `transfer_action` is optional: | Value | Behavior | | --- | --- | | `none` | Default. Do not transfer a username already used on another phone number in the same portfolio. | | `force_transfer` | Move the username from another phone number in the same portfolio to this phone number. | Successful responses return a status: | Status | Meaning | | --- | --- | | `approved` | Username has been approved and will be visible when the feature is available. | | `reserved` | Username is reserved and approved, but not yet visible to users. | Read and Delete Username[](https://dualhook.com/docs/whatsapp-business-usernames#read-and-delete-username) ----------------------------------------------------------------------------------------------------------- Read the current username: GET //username Response: { "username": "", "status": "approved" } Get reserved username suggestions: GET //username_suggestions Delete the current business username: DELETE //username Response: { "success": true } business\_username\_update Webhook[](https://dualhook.com/docs/whatsapp-business-usernames#business_username_update-webhook) ----------------------------------------------------------------------------------------------------------------------------- Meta is adding a `business_username_update` webhook field for business username status changes. { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "time": 1739321024,\ "changes": [\ {\ "field": "business_username_update",\ "value": {\ "display_phone_number": "15550783881",\ "username": "jaspersmarket",\ "status": "reserved"\ }\ }\ ]\ }\ ] } Statuses: | Status | Meaning | | --- | --- | | `approved` | Username is approved and visible to WhatsApp users. | | `reserved` | Username is reserved but not visible yet. | | `deleted` | Username was deleted. `username` can be omitted. | Subscribe each relevant app to this field if you need to track business username lifecycle. Errors[](https://dualhook.com/docs/whatsapp-business-usernames#errors) ----------------------------------------------------------------------- Common username API error codes: | Code | Meaning | | --- | --- | | `10` | App or system user lacks permission for the action. | | `33` | Invalid phone number ID, deleted WABA, or missing `whatsapp_business_management` grant. | | `100` | Invalid username format. | | `147001` | Username unavailable or failed internal checks. | | `147002` | Business portfolio is not eligible; a higher messaging limit may be required. | | `147003` | Facebook Page account is not linked. | | `147004` | Instagram account is not linked. | | `147005` | Username transfer required; retry with `force_transfer` if appropriate. | | `133010` | Business phone number is not registered for API use. | Dualhook Boundary[](https://dualhook.com/docs/whatsapp-business-usernames#dualhook-boundary) --------------------------------------------------------------------------------------------- Dualhook documents business username behavior but does not currently provide a dashboard editor for business usernames. Manage usernames in Meta Business Suite, WhatsApp Manager, WhatsApp Business app, or Meta's API. Business username events are distinct from user BSUID message-path payloads. If Meta sends business username webhooks to your integration, treat them as management metadata; they do not contain customer message content. Related ------- * [Meta BSUID, Usernames & Contact Book Transition Guide](https://dualhook.com/docs/contacts-bsuid) Overview of Meta's WhatsApp BSUID rollout: usernames, contact book behavior, parent BSUIDs, API recipient support, webhook changes, and migration timeline. * [BSUID Webhook Changes](https://dualhook.com/docs/contacts-bsuid-webhooks) WhatsApp webhook payload changes for BSUIDs, usernames, parent BSUIDs, optional phone-number fields, statuses, contacts, groups, calling, and Coexistence events. * [WhatsApp Business Accounts](https://dualhook.com/docs/whatsapp-business-accounts) What a WABA is, how it relates to phone numbers, and how ownership works. * [Phone Numbers](https://dualhook.com/docs/phone-numbers) Managing phone numbers in WhatsApp Business Platform: adding, removing, and limits. * [Business Profiles](https://dualhook.com/docs/business-profiles) Managing the public-facing WhatsApp business profile for your phone number. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Dualhook + Make: WhatsApp Webhook Integration and Runtime Sends | Dualhook Browse Docs Make Webhook Integration ======================== Use Make Custom Webhooks for direct Meta deliveries, payload-shape validation, and WhatsApp template sends. Prerequisites[](https://dualhook.com/docs/guide-make-webhook#prerequisites) ---------------------------------------------------------------------------- * An active [Dualhook connection](https://dualhook.com/docs/getting-started) with [Webhook Override](https://dualhook.com/docs/webhook-override) enabled * A Make scenario using **Webhooks > Custom webhook** * Your Meta webhook verify token (for the GET handshake — set during connection setup in Dualhook) * A per-connection Dualhook `dh_live_...` key and `PHONE_NUMBER_ID` for outbound runtime sends * Your `WABA_ID` for payload-side validation Keep these values available in Make keychains, connection fields, or secure variables: WEBHOOK_VERIFY_TOKEN DUALHOOK_API_KEY PHONE_NUMBER_ID WABA_ID META_GRAPH_VERSION=v25.0 > **No `META_APP_SECRET`?** In the Embedded Signup flow `X-Hub-Signature-256` is signed by Dualhook's Meta app and isn't customer-verifiable — validate inbound webhook payload shape instead. See [Messaging Webhook → Inbound Webhook POST Validation](https://dualhook.com/docs/messaging-webhook#signature-validation-post) > . Architecture Overview[](https://dualhook.com/docs/guide-make-webhook#architecture-overview) -------------------------------------------------------------------------------------------- In this pattern, Make is the actual Meta webhook receiver. Dualhook still handles onboarding and operations, but message-path traffic goes straight from Meta to your Custom webhook URL. Meta (messages, statuses, errors) -> Make Custom webhook -> verification -> inbound payload validation -> scenario logic -> optional send through api.dualhook.com Dualhook -> management events, template operations, health monitoring Make can also retain payloads in webhook queues, webhook logs, and scenario histories. That means Make becomes part of your privacy and retention boundary even though Dualhook itself stays metadata-only. Create the Inbound Scenario[](https://dualhook.com/docs/guide-make-webhook#create-the-inbound-scenario) -------------------------------------------------------------------------------------------------------- Set up one scenario around **Webhooks > Custom webhook**. Recommended webhook options: * **Get request headers**: on * **Get request HTTP method**: on * **JSON pass through**: on These options matter because: * you need the request method to branch cleanly between `GET` and `POST` * you need the original JSON for payload-shape validation before downstream modules act on it Do not rely on Make's API key protection for the Meta callback. That feature expects `X-Make-Apikey`, which Meta will not send. Use Meta's verify-token handshake for `GET` and payload-shape validation for `POST`. Use **JSON > Parse JSON** to expose mapped fields you can branch on. Handle the Verification Handshake[](https://dualhook.com/docs/guide-make-webhook#handle-the-verification-handshake) -------------------------------------------------------------------------------------------------------------------- Meta verifies the callback URL with a `GET` request containing: * `hub.mode=subscribe` * `hub.verify_token=` * `hub.challenge=` In the `GET` path: 1. Check that `hub.mode` is `subscribe`. 2. Check that `hub.verify_token` matches your expected token. 3. Return the raw `hub.challenge` value using **Webhooks > Webhook response**. 4. Return `403` if verification fails. Recommended response settings: * Success: `200`, plain text body = challenge * Failure: `403`, plain text body = `Forbidden` Validate the Webhook Payload[](https://dualhook.com/docs/guide-make-webhook#validate-the-webhook-payload) ---------------------------------------------------------------------------------------------------------- Validate every `POST` before downstream modules act on the payload. Confirm the WhatsApp envelope shape and the WABA ID / `phone_number_id` against the values you stored for this connection. ### Check the envelope, WABA, and phone number[](https://dualhook.com/docs/guide-make-webhook#check-the-envelope-waba-and-phone-number) After **JSON > Parse JSON**, add a **Router** or **Filter** that only accepts the request when: * `object` equals `whatsapp_business_account` * `entry[].id` equals your stored `WABA_ID` * `entry[].changes[].field` is `messages` * `entry[].changes[].value.metadata.phone_number_id` is present and equals your stored `PHONE_NUMBER_ID` If any check fails, respond `401` with **Webhook response** and stop the scenario. ### Keep the URL unguessable[](https://dualhook.com/docs/guide-make-webhook#keep-the-url-unguessable) Make's Custom webhook URL contains a random token — treat it as a secret. Do not paste it into shared chat, do not commit it to repos, and regenerate the webhook (creating a new URL) if it leaks. ### Respond quickly[](https://dualhook.com/docs/guide-make-webhook#respond-quickly) If validation passes: 1. Send `200` early with **Webhook response**. 2. Continue the business logic afterward. If validation fails: * Return `401` * Do not process the payload Send Template Messages via Dualhook[](https://dualhook.com/docs/guide-make-webhook#send-template-messages-via-dualhook) ------------------------------------------------------------------------------------------------------------------------ Make sends outbound template messages through Dualhook's allowlisted runtime. Inbound routing remains direct from Meta. Use **HTTP > Make a request**: * Method: `POST` * URL: `https://api.dualhook.com/v25.0//messages` * Body content type: `application/json` Prefer Make's dedicated credentials fields over manually placing auth inside query strings or arbitrary headers. If you use header-based auth, the request still needs: * `Authorization: Bearer ` * `Content-Type: application/json` Example template send: { "messaging_product": "whatsapp", "to": "12015550123", "type": "template", "template": { "name": "order_update_v1", "language": { "code": "en_US" } } } For templates with variables, media headers, and buttons, see [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) . Test with Dualhook[](https://dualhook.com/docs/guide-make-webhook#test-with-dualhook) -------------------------------------------------------------------------------------- 1. Copy the Make Custom webhook URL into Dualhook. 2. Use the same verify token in Dualhook and in the Make scenario. 3. Run Dualhook **Test Ping** to confirm the endpoint is reachable. 4. Send a real WhatsApp message to your number. 5. Confirm that: * the `GET` verification path works * the `POST` envelope/WABA/phone-number check accepts real Meta payloads and rejects unrelated test pings * the scenario returns `200` quickly * both inbound messages and status callbacks are processed Production Caveats[](https://dualhook.com/docs/guide-make-webhook#production-caveats) -------------------------------------------------------------------------------------- * Make Custom webhooks have a fixed payload size limit of 5 MB. Large payloads will fail before your scenario logic runs. * Make documents webhook throughput limits, queue behavior, and webhook log retention. If traffic spikes, you can see 429s, queue buildup, or delayed processing. * Use **Webhook response** intentionally. If you respond too early and a later module fails, Make can keep consuming credits while the sender already saw success. * Make queues and logs can contain payload data. Review retention and operational access carefully if you want to keep message exposure low outside Dualhook. * Use idempotent downstream handling for retried events. * Treat webhook fields as evolving. The BSUID rollout makes some identifiers conditional, so parse defensively instead of assuming `from` or `wa_id` are always present. See [BSUID Transition Guide](https://dualhook.com/docs/contacts-bsuid) . Related ------- * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. * [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) How to send template messages via Cloud API with variables, media headers, and URL buttons. * [Compliance & Data Retention](https://dualhook.com/docs/compliance) Privacy boundary, configurable retention, CSV export, and organization isolation. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp BSUID, Usernames and Contact Book Transition Guide | Dualhook Browse Docs Meta BSUID, Usernames & Contact Book Transition Guide ===================================================== Overview of Meta's WhatsApp BSUID rollout: usernames, contact book behavior, parent BSUIDs, API recipient support, webhook changes, and migration timeline. **Try the interactive checker:** Use the [BSUID Transition Checker](https://dualhook.com/tools/bsuid-checker) to see exactly which webhook fields change for your scenario. WhatsApp is adding usernames and business-scoped user IDs (BSUIDs) so a user can interact with businesses without always exposing a phone number in API payloads. This guide is the overview. The detailed implementation references are split into dedicated pages: * [BSUID webhook changes](https://dualhook.com/docs/contacts-bsuid-webhooks) * [BSUID API and sending changes](https://dualhook.com/docs/contacts-bsuid-api-changes) * [BSUID contact book and contact requests](https://dualhook.com/docs/contacts-bsuid-contact-book) * [Parent BSUIDs](https://dualhook.com/docs/contacts-bsuid-parent-user-ids) * [WhatsApp business usernames](https://dualhook.com/docs/whatsapp-business-usernames) What's Changing[](https://dualhook.com/docs/contacts-bsuid#whats-changing) --------------------------------------------------------------------------- Historically, most WhatsApp Cloud API integrations used phone numbers as the main user identifier: `wa_id` in webhooks and `to` in send requests. Meta is moving toward a model where WhatsApp users can adopt optional usernames. When a user has a username, their phone number may be omitted from API payloads unless one of Meta's phone-number inclusion conditions applies. To keep API integrations working, Meta is adding **business-scoped user IDs**, or **BSUIDs**. A BSUID uniquely identifies a WhatsApp user within the context of a business portfolio. It appears in webhook payloads as `user_id` and can be used as a recipient identifier on supported API requests. BSUIDs appear whether or not the user has adopted a username. Usernames control phone-number visibility in the app and in some payloads; BSUIDs are the backend identifier you use for continuity. Timeline[](https://dualhook.com/docs/contacts-bsuid#timeline) -------------------------------------------------------------- Meta updated its BSUID documentation on June 29, 2026 and corrected the identity-change webhook guidance on August 11, 2026. Exact rollout dates remain subject to Meta changes. | Timing | Change | | --- | --- | | Early April 2026 | BSUIDs begin appearing in messages webhooks. | | Early April 2026 | Meta contact book starts rolling out. | | July 2026 | Messages API and Marketing Messages API add BSUID recipient support. | | Early July 2026 | `REQUEST_CONTACT_INFO` support becomes available for requesting phone numbers. | | Later 2026 | WhatsApp user usernames become available. | | Later 2026 | Business username adopt/change APIs become available. | If your production behavior depends on a specific rollout date, confirm current availability in Meta's live documentation and in your App Dashboard test webhooks. Identifier Model[](https://dualhook.com/docs/contacts-bsuid#identifier-model) ------------------------------------------------------------------------------ | Identifier | Meaning | | --- | --- | | `wa_id` | The user's phone number. Can be omitted when the user has a username and phone-number inclusion conditions are not met. | | `user_id` | The user's BSUID scoped to one business portfolio. | | `parent_user_id` | A parent BSUID shared across enrolled business portfolios. Only present for eligible managed businesses that have enrolled. | | `username` | The user's optional WhatsApp username. Changing it does not change phone number or BSUID. | | `recipient` | New API request field that accepts a BSUID or parent BSUID on supported endpoints. | | `to` | Existing phone-number recipient field. If both `to` and `recipient` are sent, `to` takes precedence. | BSUID format is a two-letter ISO 3166 alpha-2 country code, a period, and up to 128 alphanumeric characters, for example `US.13491208655302741918`. Parent BSUIDs add `ENT` after the country code, for example `US.ENT.11815799212886844830`. Use the full BSUID value exactly as received. Do not strip the country code or period. Implementation Areas[](https://dualhook.com/docs/contacts-bsuid#implementation-areas) -------------------------------------------------------------------------------------- ### Webhook Parsing[](https://dualhook.com/docs/contacts-bsuid#webhook-parsing) Update webhook handlers so they do not require phone-number fields. Incoming messages, statuses, system events, contact messages, user preferences, groups, calls, history, and app echoes can all carry BSUID-related fields. Start with [BSUID webhook changes](https://dualhook.com/docs/contacts-bsuid-webhooks) . ### Sending and API Requests[](https://dualhook.com/docs/contacts-bsuid#sending-and-api-requests) Supported APIs add `recipient` for BSUID or parent BSUID sends. Phone-number sends still use `to`, and `to` wins if both are present. Start with [BSUID API and sending changes](https://dualhook.com/docs/contacts-bsuid-api-changes) . ### Contact Recovery[](https://dualhook.com/docs/contacts-bsuid#contact-recovery) If you still need phone numbers, use Meta's contact book behavior and `REQUEST_CONTACT_INFO` flows rather than assuming `wa_id` always exists. Start with [BSUID contact book and contact requests](https://dualhook.com/docs/contacts-bsuid-contact-book) . ### Multi-Portfolio Identity[](https://dualhook.com/docs/contacts-bsuid#multi-portfolio-identity) If you operate multiple business portfolios and need one stable user identifier across them, evaluate parent BSUIDs. Start with [Parent BSUIDs](https://dualhook.com/docs/contacts-bsuid-parent-user-ids) . ### Business Username Management[](https://dualhook.com/docs/contacts-bsuid#business-username-management) Business usernames are separate from user privacy usernames. They make a business searchable by username, but they do not hide the business phone number. Start with [WhatsApp business usernames](https://dualhook.com/docs/whatsapp-business-usernames) . When Phone Numbers Still Appear[](https://dualhook.com/docs/contacts-bsuid#when-phone-numbers-still-appear) ------------------------------------------------------------------------------------------------------------ If a user enables a username, their phone number is still included in webhooks when at least one of these conditions applies: 1. You have messaged or called the user's phone number within the last 30 days of the webhook. 2. You have received a message or call from the user's phone number within the last 30 days of the webhook. 3. The user is in your Meta-hosted contact book. The 30-day lookback is evaluated per business phone number. If one number in a portfolio interacted with the user, that does not automatically make the phone number appear for webhooks tied to another business phone number. The contact book is portfolio-scoped and can make phone numbers available across phone numbers in the same portfolio once populated. Dualhook Boundary[](https://dualhook.com/docs/contacts-bsuid#dualhook-boundary) -------------------------------------------------------------------------------- Dualhook uses Webhook Override so message-path webhooks go directly from Meta to your endpoint. That matters for BSUID adoption: * Dualhook does not ingest your `messages` webhooks. * Dualhook does not store BSUIDs from message-path payloads as product data. * Dualhook does not populate or manage Meta's contact book for you. * Dualhook does not migrate your CRM identifiers automatically. * Dualhook can help by documenting the field changes, surfacing operational context, and providing the [BSUID Transition Checker](https://dualhook.com/tools/bsuid-checker) . Your webhook receiver, CRM, campaign tools, support tools, and analytics pipelines must be updated to store and join on BSUIDs. Migration Checklist[](https://dualhook.com/docs/contacts-bsuid#migration-checklist) ------------------------------------------------------------------------------------ 1. Add `user_id` and `parent_user_id` columns or equivalent identifier storage. 2. Stop treating `wa_id`, `from`, `recipient_id`, and `phone_number` as required fields. 3. Update inbound message, status, system, group, calling, history, echo, and preference webhook parsers. 4. Update send code to support `recipient` while keeping `to` for known phone numbers. 5. Handle error `131062` for message types that do not support BSUID recipients. 6. Decide how your CRM maps phone numbers, BSUIDs, parent BSUIDs, and usernames. 7. Add contact-request flows if your business process still needs phone numbers. 8. Test username and no-phone-number scenarios in App Dashboard webhook testing. 9. Merge `user_changed_number` and `user_changed_user_id` system events by their previous and current BSUID fields; do not attempt to subscribe to `user_id_update`, which Meta says is not a webhook field. FAQ[](https://dualhook.com/docs/contacts-bsuid#faq) ---------------------------------------------------- **What is a BSUID?** A business-scoped user ID. It uniquely identifies a WhatsApp user within one business portfolio and appears as `user_id` in webhook payloads. **Is a BSUID global?** No. A regular BSUID is scoped to a business portfolio. The same WhatsApp user has different regular BSUIDs for different business portfolios. Parent BSUIDs are the cross-portfolio option for eligible managed businesses. **Does a username change the BSUID?** No. A user can change their username without changing their phone number or BSUID. A phone-number change can regenerate the BSUID. **Can I keep using phone numbers?** Yes, when you know the phone number and Meta is allowed to include it. For supported sends, `to` still works and takes precedence over `recipient`. Your webhook parser still needs to handle payloads where the phone number is omitted. **Can I use BSUIDs for authentication templates?** Not for one-tap, zero-tap, or copy-code authentication templates. Those still require phone numbers. Meta returns error `131062` when a BSUID recipient is unsupported for the message type. **Does Dualhook see or store my customers' BSUIDs?** Not for message-path traffic. Meta routes message webhooks directly to your server through Webhook Override, and Dualhook does not store those payloads. Want to see these field changes for your specific scenario? Configure your setup and get real-time webhook field predictions. [Open the BSUID Transition Checker](https://dualhook.com/tools/bsuid-checker) Related ------- * [BSUID Webhook Changes](https://dualhook.com/docs/contacts-bsuid-webhooks) WhatsApp webhook payload changes for BSUIDs, usernames, parent BSUIDs, optional phone-number fields, statuses, contacts, groups, calling, and Coexistence events. * [BSUID API and Sending Changes](https://dualhook.com/docs/contacts-bsuid-api-changes) How WhatsApp APIs use BSUIDs and parent BSUIDs in the recipient field for messages, Marketing Messages API, groups, block users, calling, and SIP. * [BSUID Contact Book and Contact Requests](https://dualhook.com/docs/contacts-bsuid-contact-book) Meta contact book behavior for WhatsApp BSUIDs, phone-number inclusion rules, contact book deletion, REQUEST\_CONTACT\_INFO, and contacts webhooks. * [Parent BSUIDs](https://dualhook.com/docs/contacts-bsuid-parent-user-ids) Parent BSUID behavior for eligible multi-portfolio WhatsApp businesses, including payload fields, account lookup, supported APIs, and limitations. * [WhatsApp Business Usernames](https://dualhook.com/docs/whatsapp-business-usernames) Business username rules for WhatsApp business phone numbers, including format, display priority, reserved usernames, management APIs, webhook updates, and errors. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Marketing Max-Price (Beta) | Dualhook Browse Docs Marketing Max-Price (Beta) ========================== Set a maximum price per marketing message delivery on MM API for WhatsApp using bid\_spec. Includes per-message multiplier, reach estimation, and rollout phases. What Max-Price Is[](https://dualhook.com/docs/marketing-max-price#what-max-price-is) ------------------------------------------------------------------------------------- The **max-price** feature on [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) lets you set the maximum price you are willing to pay per marketing message delivery. Meta charges that max-price or lower for each delivered message — never higher. The bid is encoded as a `bid_spec` object on the template. There is no change to Meta's per-message pricing model. Max-price is an opt-in optimization knob — you can set it equal to, lower than, or higher than the published rate card to shape your delivery and cost trade-offs. Rollout Status[](https://dualhook.com/docs/marketing-max-price#rollout-status) ------------------------------------------------------------------------------- | Phase | Window | Who can use | | --- | --- | --- | | Limited Beta | Mid-May 2026 onward | Any partner or directly-integrated business — limited number of clients per partner | | Open Beta | October 2026, exact date TBC | Available to all businesses | | GA | Q2 2027 | Required in eligible geographies. Fixed published rates only apply on Cloud API thereafter | The per-message multiplier (see below) is **subject to change during beta**. Prerequisites[](https://dualhook.com/docs/marketing-max-price#prerequisites) ----------------------------------------------------------------------------- * WABA onboarded to MM API (see [MM API Onboarding](https://dualhook.com/docs/marketing-messages-onboarding) ) * WABA in a country eligible for MM API * Sending through `/marketing_messages` (Cloud API rejects templates with `bid_spec` — error 131061) How Bidding Works[](https://dualhook.com/docs/marketing-max-price#how-bidding-works) ------------------------------------------------------------------------------------- `bid_amount` represents your max price **per 1,000 deliveries**, expressed in your WABA currency's smallest unit (cents for USD, paise for INR, centavos for MXN, and so on). Three positioning strategies: | Bid relative to rate card | Likely outcome | | --- | --- | | Equal to published rate | Similar delivery volumes, lower realized cost on optimizable inventory | | Lower than published rate | Reach broader, less-engaged user cohorts at lower cost per delivery | | Higher than published rate | Higher delivery rates during peak periods (holidays, sales) at higher cost | Meta's recommendation: set the bid at the **template level**, then use the per-message multiplier sparingly. Optimization performs better against the original template-level bid than against multiplier-adjusted bids on every send. Create a Template with bid\_spec[](https://dualhook.com/docs/marketing-max-price#create-a-template-with-bid_spec) ------------------------------------------------------------------------------------------------------------------ curl 'https://graph.facebook.com///message_templates' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "seasonal_sale_promo", "category": "MARKETING", "language": "en", "components": [\ { "type": "BODY", "text": "Shop our seasonal sale! Up to 50% off selected items." }\ ], "bid_spec": { "bid_amount": "87000", "bid_strategy": "LOWEST_COST_WITH_BID_CAP" } }' If `bid_spec` is omitted, the template uses standard rate card pricing — no bidding at all. If `bid_spec` is present at template creation, it cannot be removed later (you'd have to create a new template without it). `bid_strategy` is currently always `LOWEST_COST_WITH_BID_CAP`. Calculating bid\_amount[](https://dualhook.com/docs/marketing-max-price#calculating-bid_amount) ------------------------------------------------------------------------------------------------ Two-step conversion: 1. Convert your desired per-delivery price to the currency's smallest unit. 2. Multiply by 1,000 (because the field is per 1,000 deliveries, not per delivery). | Desired per-delivery price | Currency unit | Per-1k value | `bid_amount` | | --- | --- | --- | --- | | ₹0.87 | paise | 87 | `87000` | | $0.05 | cents | 5 | `5000` | | $0.087 | cents | 8.7 | `8700` | | ₹2.50 | paise | 250 | `250000` | Be precise — small rounding errors compound over high volume. Retrieve and Update Max-Price[](https://dualhook.com/docs/marketing-max-price#retrieve-and-update-max-price) ------------------------------------------------------------------------------------------------------------- **Retrieve:** curl 'https://graph.facebook.com//?fields=bid_spec' \ -H 'Authorization: Bearer ' { "bid_spec": { "bid_strategy": "LOWEST_COST_WITH_BID_CAP", "bid_amount": 87000 }, "id": "1733678867511493" } **Update:** curl 'https://graph.facebook.com//' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "bid_spec": { "bid_strategy": "LOWEST_COST_WITH_BID_CAP", "bid_amount": 4000 } }' You can update `bid_spec` only on templates that were originally created with it. Adding `bid_spec` to a template that did not originally have it is not supported — create a new template. Standard template edit limits apply: 100 edits/hour and 2,400/day on approved templates; unlimited on rejected or paused templates. Per-Message Multiplier[](https://dualhook.com/docs/marketing-max-price#per-message-multiplier) ----------------------------------------------------------------------------------------------- Per-send adjustment is supported via `bid_spec.per_message_bid_multiplier` on the `/marketing_messages` request: { "recipient_type": "individual", "messaging_product": "whatsapp", "to": "", "type": "template", "template": { "name": "seasonal_sale_promo", "language": { "code": "en" } }, "bid_spec": { "per_message_bid_multiplier": "1.5" } } The multiplier is a positive float applied to the template's `bid_amount`: * `1.5` → effective max-price is 150% of template's bid * `0.5` → 50% of template's bid * `1` (default) → unchanged A template with `bid_amount: 2000` sent with `per_message_bid_multiplier: 1.5` has an effective max-price of 3000. **Caveat from Meta:** the multiplier is subject to change during beta, and delivery performance often differs from setting the equivalent template-level bid directly even when the effective max-price is the same. Prefer template-level bids for predictability. Reach Estimation[](https://dualhook.com/docs/marketing-max-price#reach-estimation) ----------------------------------------------------------------------------------- Meta provides a reach-estimation endpoint that returns expected delivery and cost ranges across multiple bid amounts based on historical data: curl 'https://graph.facebook.com///reachestimate?targeting_spec={"geo_locations":{"countries":["IN"]}}&date_interval=L7D' \ -H 'Authorization: Bearer ' Parameters: * `targeting_spec` (required) — serialized JSON with `geo_locations.countries`, an array of ISO country codes * `date_interval` (required) — historical lookback: `L1D`, `L7D`, `L14D`, `L28D` Response: { "waba_currency": "USD", "estimates": [\ {\ "bid_amount": 400,\ "users": 1000,\ "lower_bound_deliveries": 500,\ "upper_bound_deliveries": 570,\ "cost_lower_bound": 389.74,\ "cost_upper_bound": 390.74\ },\ {\ "bid_amount": 520,\ "users": 1000,\ "lower_bound_deliveries": 600,\ "upper_bound_deliveries": 650,\ "cost_lower_bound": 400.74,\ "cost_upper_bound": 510.74\ }\ ] } `users` is fixed at 1,000 during beta. Cost bounds are in the WABA currency's smallest unit. Estimates are informational only — actual results depend on platform conditions. Metrics and Billing[](https://dualhook.com/docs/marketing-max-price#metrics-and-billing) ----------------------------------------------------------------------------------------- Sends with or without `bid_spec` use the same Marketing Lite SKU for billing. They surface in analytics as: | API | Field | Value for max-price sends | | --- | --- | --- | | Pricing Analytics | `pricing_category` | `MARKETING_LITE` (uppercase) | | Template Analytics | `product_type` | `MARKETING_MESSAGES_LITE_API` | | Status webhooks | `pricing.category` | `marketing_lite` (lowercase) | Webhooks use lowercase, analytics APIs use uppercase. Account for both casings in any code that joins these signals. Errors[](https://dualhook.com/docs/marketing-max-price#errors) --------------------------------------------------------------- | Code | Meaning | Fix | | --- | --- | --- | | `131061` | Templates with `bid_spec` cannot be sent through Cloud API `/messages` | Send through `/marketing_messages` | | `100` | Need to sign the testing legal agreement before sending | Sign Meta's testing legal agreement to gain feature access | Operational Notes[](https://dualhook.com/docs/marketing-max-price#operational-notes) ------------------------------------------------------------------------------------- * **Ramp gradually.** When sending with a new `bid_spec` template for the first time, increase volume slowly. Aligns with template pacing best practices and gives the delivery system signal to optimize. * **Plan for GA.** Once max-price reaches GA in Q2 2027, fixed published rates will only apply on Cloud API. WABAs sending high marketing volume should treat max-price as on the migration path now, not a curiosity. * **Dualhook does not currently expose `bid_spec` in its template management UI.** If you create or update a template directly via Meta with `bid_spec`, Dualhook will sync the template's other fields, but managing `bid_spec` itself remains a Meta Graph API operation for now. Related ------- * [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) Reference for Meta's separate /marketing\_messages product, which is not exposed by Dualhook's runtime or Embedded Signup configuration. * [Marketing Messages](https://dualhook.com/docs/marketing-messages) Marketing template messaging through Dualhook's standard Cloud API runtime, plus boundaries for Meta's separate MM API product. * [Pricing](https://dualhook.com/docs/pricing) Dualhook subscription plans, what is included, and how pricing relates to Meta messaging fees. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp BSUID Webhook Changes: user_id, wa_id, username and parent_user_id | Dualhook Browse Docs BSUID Webhook Changes ===================== WhatsApp webhook payload changes for BSUIDs, usernames, parent BSUIDs, optional phone-number fields, statuses, contacts, groups, calling, and Coexistence events. This page covers BSUID-related webhook payload changes. For the high-level transition guide, start with [Meta BSUID, Usernames & Contact Book Transition Guide](https://dualhook.com/docs/contacts-bsuid) . Scope[](https://dualhook.com/docs/contacts-bsuid-webhooks#scope) ----------------------------------------------------------------- The changes affect more than the basic incoming `messages` webhook. BSUID, parent BSUID, username, and optional phone-number fields can appear across: * incoming messages, * sent, delivered, read, and failed status webhooks, * `user_preferences`, * system messages that report BSUID or phone-number changes, * contacts messages from `REQUEST_CONTACT_INFO`, * Coexistence `history`, `smb_message_echoes`, and `smb_app_state_sync`, * Groups API webhooks, * Calling API webhooks. Quick Reference[](https://dualhook.com/docs/contacts-bsuid-webhooks#quick-reference) ------------------------------------------------------------------------------------- ### Outbound Status Webhooks[](https://dualhook.com/docs/contacts-bsuid-webhooks#outbound-status-webhooks) For sent, delivered, and read statuses: | Identifier | Sent to phone number | Sent to BSUID | | --- | --- | --- | | `wa_id` | Always included | Included only if phone number is available under Meta's phone-number rules. | | `user_id` | Always included | Always included. | | `recipient_user_id` | Always included | Always included. | | `parent_user_id` | Included if parent BSUIDs are enabled. | Included if parent BSUIDs are enabled. | | `recipient_parent_user_id` | Included if parent BSUIDs are enabled. | Included if parent BSUIDs are enabled. | | `username` | Included in delivered/read if the user has a username. | Included in delivered/read if the user has a username. | For failed status webhooks, the `contacts` array is omitted. `recipient_user_id` is also omitted if the failed message was sent to a phone number. ### Incoming Messages[](https://dualhook.com/docs/contacts-bsuid-webhooks#incoming-messages) | Identifier | User has username | User does not have username | | --- | --- | --- | | `wa_id` | Included only if phone number is available under Meta's phone-number rules. | Always included. | | `user_id` | Always included. | Always included. | | `parent_user_id` | Included if parent BSUIDs are enabled. | Included if parent BSUIDs are enabled. | | `username` | Included. | Omitted. | Incoming Messages[](https://dualhook.com/docs/contacts-bsuid-webhooks#incoming-messages-1) ------------------------------------------------------------------------------------------- Incoming message payloads add BSUID fields while making phone-number fields conditional. | Field | Behavior | | --- | --- | | `contacts[].profile.username` | Present if the user has adopted a username. | | `contacts[].wa_id` | Can be omitted if the user has a username and phone-number conditions are not met. | | `contacts[].user_id` | The user's BSUID. | | `contacts[].parent_user_id` | Parent BSUID when enabled. | | `messages[].from` | Can be omitted under the same conditions as `wa_id`. | | `messages[].from_user_id` | The user's BSUID. | | `messages[].from_parent_user_id` | Parent BSUID when enabled. | | `messages[].group_id` | Present for incoming group messages. | Minimal username-era incoming message shape: { "contacts": [\ {\ "profile": {\ "name": "Sheena Nelson",\ "username": "realsheenanelson"\ },\ "user_id": "US.13491208655302741918",\ "parent_user_id": "US.ENT.11815799212886844830"\ }\ ], "messages": [\ {\ "from_user_id": "US.13491208655302741918",\ "from_parent_user_id": "US.ENT.11815799212886844830",\ "id": "wamid...",\ "timestamp": "1749416383",\ "type": "text",\ "text": {\ "body": "Does it come in another color?"\ }\ }\ ] } Status Messages[](https://dualhook.com/docs/contacts-bsuid-webhooks#status-messages) ------------------------------------------------------------------------------------- Status webhooks add a `contacts` array for sent, delivered, and read statuses. Failed statuses omit `contacts`. | Field | Behavior | | --- | --- | | `contacts[].profile.name` | User display name. | | `contacts[].profile.username` | Present for delivered/read if the user has a username. Omitted for sent. | | `contacts[].wa_id` | Included only when Meta can include the phone number. | | `contacts[].user_id` | The user's BSUID. | | `contacts[].parent_user_id` | Parent BSUID when enabled. | | `statuses[].recipient_id` | Phone number or group ID when available; can be omitted for BSUID sends. | | `statuses[].recipient_user_id` | The user's BSUID. Omitted on failed statuses if the original send used a phone number. | | `statuses[].recipient_parent_user_id` | Parent BSUID when enabled. | Your delivery-status pipeline should not require `contacts[]`, `contacts[].wa_id`, or `statuses[].recipient_id`. System and User ID Updates[](https://dualhook.com/docs/contacts-bsuid-webhooks#system-and-user-id-updates) ----------------------------------------------------------------------------------------------------------- When a WhatsApp user's phone number or BSUID changes, Meta reports the change on the existing `messages` subscription as a system message. Handle both current variants: | `system.type` | Meaning | Important fields | | --- | --- | --- | | `user_changed_number` | The user changed phone number and Meta can share the new number. | `wa_id`, `user_id`, `previous_user_id`, and parent equivalents when enabled. | | `user_changed_user_id` | The BSUID changed and Meta cannot share the phone number. | `user_id`, `previous_user_id`, and parent equivalents when enabled; do not require `wa_id`. | Meta corrected its documentation on August 11, 2026: `user_id_update` is **not** a subscribable webhook field and does not appear in App Dashboard. Do not wait for or attempt to subscribe to that field. Store the previous and current IDs from the system message so you can merge CRM identities instead of creating a new customer thread. Contact and Preference Webhooks[](https://dualhook.com/docs/contacts-bsuid-webhooks#contact-and-preference-webhooks) --------------------------------------------------------------------------------------------------------------------- `REQUEST_CONTACT_INFO` and direct contact-sharing messages trigger a contacts message. The message adds: * `messages[].from_user_id`, * `messages[].contacts[].origin` with `contact_request` or `other`, * `messages[].contacts[].vcard` when the user shared a contact directly (`origin: "other"`), * phone number details in `messages[].contacts[].phones[]`. `user_preferences` webhooks add `user_id` and `parent_user_id` both in `contacts[]` and `user_preferences[]`. Their `wa_id` fields can be omitted when phone-number inclusion rules are not met. Coexistence Fields[](https://dualhook.com/docs/contacts-bsuid-webhooks#coexistence-fields) ------------------------------------------------------------------------------------------- Coexistence direct-routed fields also gain BSUID data: | Field | BSUID changes | | --- | --- | | `history` | Threads can include `context.user_id`, `context.parent_user_id`, and `context.username`. Messages can include `from_user_id` and `from_parent_user_id`. | | `smb_message_echoes` | Adds `contacts[]`, `to_user_id`, and `to_parent_user_id`; `to` can be omitted. | | `smb_app_state_sync` | Contact objects can include `user_id`, `parent_user_id`, and `username`; `phone_number` can be omitted. | | Consumer `revoke` and `edit` messages | Add `from_user_id`, `from_parent_user_id`, and optional `username` in contacts. | In Dualhook setups, `history`, `smb_message_echoes`, and `smb_app_state_sync` go directly to your endpoint via Webhook Override. Groups and Calling Webhooks[](https://dualhook.com/docs/contacts-bsuid-webhooks#groups-and-calling-webhooks) ------------------------------------------------------------------------------------------------------------- Group and calling surfaces also change: * group delivered/read status webhooks add contact `user_id`, parent BSUID, and username fields; * `recipient_participant_id` can be omitted, while `recipient_participant_user_id` is added; * `group_participants_update` payloads add `user_id`, `parent_user_id`, and `username` for participant events; * calling `calls` webhooks add `from_user_id`, `to_user_id`, `recipient_user_id`, and parent equivalents depending on direction and event type; * SIP signaling can carry BSUIDs in SIP URIs and `x-wa-meta-user-id`, `x-wa-meta-parent-user-id`, and `x-wa-meta-username` headers. If you use groups or calling, update those parsers too. This transition is not limited to message text webhooks. Webhook Testing[](https://dualhook.com/docs/contacts-bsuid-webhooks#webhook-testing) ------------------------------------------------------------------------------------- Meta's App Dashboard webhook tester supports username adoption scenarios for message webhooks: * user has not adopted a username: BSUID and phone-number fields are present, username omitted; * user has adopted a username and phone number is unavailable: username and BSUID fields are present, phone-number fields are omitted; * user has adopted a username and phone number is available: username, BSUID, and phone-number fields are present; * parent BSUID present: parent BSUID fields appear alongside regular BSUIDs. Use these tests against your real webhook handler, not only a schema validator. Dualhook Handling[](https://dualhook.com/docs/contacts-bsuid-webhooks#dualhook-handling) ----------------------------------------------------------------------------------------- Dualhook does not ingest message-path BSUID payloads. With Webhook Override, Meta sends `messages`, `history`, `smb_message_echoes`, and `smb_app_state_sync` directly to your endpoint. Dualhook cannot replay missed BSUID webhook payloads or backfill BSUID mappings into your CRM. Treat BSUID support as an application-side migration in your webhook receiver and downstream systems. Related ------- * [Meta BSUID, Usernames & Contact Book Transition Guide](https://dualhook.com/docs/contacts-bsuid) Overview of Meta's WhatsApp BSUID rollout: usernames, contact book behavior, parent BSUIDs, API recipient support, webhook changes, and migration timeline. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. * [Webhook Events & Notifications](https://dualhook.com/docs/webhook-events) Management webhook events: template status, quality updates, account alerts, and forwarding. * [history Webhook](https://dualhook.com/docs/webhook-history) Field reference for WhatsApp history webhooks: Coexistence chat-history sync, phases, chunks, declined sharing, direct routing, and Dualhook handling. * [smb\_message\_echoes Webhook](https://dualhook.com/docs/webhook-smb-message-echoes) Field reference for WhatsApp smb\_message\_echoes webhooks: Business App sent-message echoes, edits, revokes, direct routing, and Dualhook handling. * [smb\_app\_state\_sync Webhook](https://dualhook.com/docs/webhook-smb-app-state-sync) Field reference for WhatsApp smb\_app\_state\_sync webhooks: Coexistence contact sync, add/remove actions, direct routing, and Dualhook handling. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Calling API Reference: Endpoints, Webhooks, WebRTC | Dualhook Browse Docs Calling API Reference ===================== Consolidated WhatsApp Calling reference: every endpoint, every webhook, WebRTC and ICE guidelines, DTLS certificate recommendations, SDP requirements, bandwidth budget, IP allowlisting. What This Page Covers[](https://dualhook.com/docs/calling-api-reference#what-this-page-covers) ----------------------------------------------------------------------------------------------- A consolidated reference for the Calling API: every endpoint, every webhook, and the media-path guidelines you need to build a working WebRTC integration. For task-oriented walkthroughs see [Business-Initiated Calls](https://dualhook.com/docs/calling-business-initiated) , [User-Initiated Calls](https://dualhook.com/docs/calling-user-initiated) , [Configuration](https://dualhook.com/docs/calling-configuration) , [Permissions](https://dualhook.com/docs/calling-permissions) , and [SIP](https://dualhook.com/docs/calling-sip) . Endpoints Summary[](https://dualhook.com/docs/calling-api-reference#endpoints-summary) --------------------------------------------------------------------------------------- All calling endpoints are scoped to a business phone number. | Method + Endpoint | Purpose | | --- | --- | | `POST //settings` | [Configure call settings](https://dualhook.com/docs/calling-configuration)
(status, call\_hours, callback\_permission\_status, audio codecs, SIP, SRTP key exchange) | | `GET //settings` | [Read current settings](https://dualhook.com/docs/calling-configuration#get-current-settings)
. Add `?include_sip_credentials=true` to include SIP password | | `POST //calls` | All call actions: `connect` (BIC), `pre_accept`/`accept`/`reject` (UIC), `terminate` (both) | | `GET //call_permissions?user_wa_id=` | [Get call permission state](https://dualhook.com/docs/calling-permissions#get-call-permission-state)
— current status, expiration, action limits | | `POST //messages` (interactive `voice_call`) | [Send a free-form call button message](https://dualhook.com/docs/calling-call-buttons#interactive-voice_call-message-free-form) | | `POST //messages` (interactive `call_permission_request`) | [Send a free-form permission request](https://dualhook.com/docs/calling-permissions#free-form-call-permission-request) | | `POST //message_templates` (with `voice_call` button) | [Create a call-button template](https://dualhook.com/docs/calling-call-buttons#voice_call-template-button) | | `POST //message_templates` (with `call_permission_request` component) | [Create a permission-request template](https://dualhook.com/docs/calling-permissions#template-call-permission-request) | | `GET //?fields=call_analytics` | Get cost, completed-call counts, average call duration ([Pricing](https://dualhook.com/docs/calling-pricing)
) | `POST //calls` carries different payloads depending on `action`. Common shape: { "messaging_product": "whatsapp", "to": "12015550123", // BIC only "call_id": "wacid.HBgL...", // pre_accept/accept/reject/terminate "action": "connect | pre_accept | accept | reject | terminate", "session": { "sdp_type": "offer | answer", "sdp": "<>" }, "biz_opaque_callback_data": "0fS5cePMok" // optional } `session` is required on `connect` (offer), `pre_accept` (answer), `accept` (answer). Not used on `reject` or `terminate`. Webhooks Summary[](https://dualhook.com/docs/calling-api-reference#webhooks-summary) ------------------------------------------------------------------------------------- Subscribe to the `calls` webhook field to receive these. In Dualhook setups they're delivered directly to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) — Dualhook does not ingest them. | Webhook | When | Notes | | --- | --- | --- | | Call Connect (UIC) | User initiates a call | Contains SDP offer to apply to your WebRTC stack | | Call Connect (BIC) | After your `POST /calls connect` succeeds | Contains SDP answer from Meta | | Call Status (BIC) | `RINGING`, `ACCEPTED`, `REJECTED` | `ACCEPTED` typically arrives after media is already established — for auditing | | Call Terminate | Either party hangs up, you call `terminate`/`reject`, or call fails | Includes `start_time`, `end_time`, `duration` if call was answered. `errors[]` if it failed. | | User Call Permission Reply (`messages` field) | User accepts, rejects, or auto-revokes permission | Carries `is_permanent`, `expiration_timestamp`, `response_source` | Note: the user permission reply arrives on the **`messages`** field (not `calls`) because it's a user response to your message. Subscribe to both fields. The `account_settings_update` webhook field tracks changes to `calling.status`, `calling.call_icon_visibility`, `calling.callback_permission_status`, `calling.sip.status`, and `calling.srtp_key_exchange_protocol`. See [Configuration → Settings Webhook](https://dualhook.com/docs/calling-configuration#settings-webhook-account_settings_update) . Media Path Guidelines[](https://dualhook.com/docs/calling-api-reference#media-path-guidelines) ----------------------------------------------------------------------------------------------- Mandatory for all media — failures here either cause call failure during signaling or media decoding: * **OPUS audio codec only** (or PCMA/PCMU if explicitly enabled in `audio.additional_codecs` for transcoding interop). * **Media clock rate: 48 kHz**. * **DTMF clock rate: 8 kHz** (only). * **ptime: 20ms**. * **Single audio SSRC**. Meta's relay overwrites your business audio SSRC to a fixed value before relaying to the WhatsApp client. Multiple SSRCs cause undefined behavior — corruption, glitches, total media failure. Recommended (not mandatory but produces good call quality): * ECDSA certificates for DTLS (faster cert generation, shorter handshake, no fragmentation). * Pre-accept (UIC) to frontload the WebRTC connection before media flows. ICE and WebRTC[](https://dualhook.com/docs/calling-api-reference#ice-and-webrtc) --------------------------------------------------------------------------------- Meta's WebRTC stack is **ICE-LITE**. Your stack should be **ICE-FULL** to maintain compatibility: * Initiate ICE connectivity checks via STUN. * Take the **CONTROLLING role**. Meta only takes CONTROLLED. Starting CONTROLLED can stall and timeout, or take longer due to role-conflict resolution. * Use **regular nomination**, not aggressive nomination (RFC 5245). * Wait for the ICE process to complete before nominating the candidate and starting DTLS. * Don't switch the candidate mid-call. ICE Trickle is supported — Meta will accept STUN connectivity checks from addresses not included in your initial SDP, as long as STUN message integrity passes. Meta does **not** provide STUN/TURN servers and has no recommendations on third-party providers. If your media terminates in your own infra (typical for IVR/contact center setups), STUN/TURN often isn't needed — your VIP behind a load balancer can serve as the ICE candidate. DTLS Certificates[](https://dualhook.com/docs/calling-api-reference#dtls-certificates) --------------------------------------------------------------------------------------- * Use **ECDH keys** to prevent packet fragmentation during handshake transmission. * Your business should act as the **DTLS client** (RFC 6347 Section 4.2). * Common DTLS retry intervals (1s, 2s, 4s) cause ~3 seconds of delayed setup after Meta receives your SDP, which can result in audio clipping. Switch to SDES key exchange to avoid this — see [Calling Troubleshooting → Audio Clipping](https://dualhook.com/docs/calling-troubleshooting#audio-clipping) . SDP Overview[](https://dualhook.com/docs/calling-api-reference#sdp-overview) ----------------------------------------------------------------------------- Session Description Protocol describes media parameters for WebRTC negotiation: codecs, ICE candidates, DTLS fingerprints, etc. Your `session.sdp` field on `POST /calls` must be RFC 8866 compliant. Key requirements: * **Single stream per SDP**. Multiple streams not supported. * **One audio track per stream**. * **CRLF (`\r\n`) line endings** within the SDP string. Don't double-serialize — `session.sdp` is just a string field. * **DTMF payload type 126** with 8000 clock rate (default if absent). * **`a=fingerprint` line** when using DTLS for SRTP key exchange. Without it, you'll get "No fingerprint found in SDP" — switch to SDES if you can't include a fingerprint. For a business agent's browser to work in UIC, the WebRTC agent in the browser should generate an SDP **answer** (not offer). Pass that answer to `POST /calls accept`. Meta cannot work with any SDP offer other than the one it generates and provides on the UIC connect webhook. Media Relay Targeting (Latency)[](https://dualhook.com/docs/calling-api-reference#media-relay-targeting-latency) ----------------------------------------------------------------------------------------------------------------- Meta's targeting algorithm picks the Meta relay closest to the WhatsApp consumer. That relay's IP is the ICE candidate Meta sends in the SDP. **On your side**, place media servers in the same region as the consumer to minimize latency. For international calls, this minimizes the public-internet routing length. Optimize against the candidate IPs on Meta's SDPs, not against the source of signaling endpoints (which can be different). Bandwidth Budget[](https://dualhook.com/docs/calling-api-reference#bandwidth-budget) ------------------------------------------------------------------------------------- Per call: | Codec | Bandwidth | | --- | --- | | OPUS | ~40 kbps codec + ~20 kbps overhead = ~60 kbps total | | G.711 (PCMA/PCMU) | 64 kbps codec + 20 kbps overhead = ~84 kbps total | Multiply by concurrent calls. A 1 Mbps link supports roughly: * ~15 concurrent OPUS calls * ~12 concurrent G.711 calls OPUS is variable-rate based on network conditions. A 1-minute OPUS call uses ~3.75 MB; G.711 uses ~4.9 MB. Maximum concurrent calls per phone number: **1,000**. Webhook Delivery Semantics[](https://dualhook.com/docs/calling-api-reference#webhook-delivery-semantics) --------------------------------------------------------------------------------------------------------- * **No ordering guarantees** for calling webhooks. Terminate can arrive before connect if the user hangs up immediately. Connect retries can interleave with terminate sends. Use the `timestamp` field to order events. * **No exactly-once delivery**. Be prepared for duplicates. Common causes: 20-second HTTP timeout from Meta's side (your endpoint thinks it succeeded; Meta retries), multiple apps subscribed to `calls` (one app fails, Meta retries the whole dispatch hitting the others again), Meta queue infrastructure recovery. * **Retry policy** is shorter than messaging webhooks (which retry up to 7 days). Meta hasn't published exact numbers — assume stale webhooks may still arrive and check `timestamp` before acting. * **Multiple webhook endpoints** (one app per URL) get all calling webhooks dispatched to all of them. No notion of primary/secondary — all are equal. * **Different URLs for messaging vs calling** is achievable by using two separate Meta apps (one subscribed to `messages`, one to `calls`) and overriding callback URLs per app. Or just use one app and one URL — distinguish by payload field (`field: "calls"` vs `field: "messages"`). IP Allowlisting[](https://dualhook.com/docs/calling-api-reference#ip-allowlisting) ----------------------------------------------------------------------------------- Meta's webhook servers come from a fluid set of IP ranges. As of mid-2024, this collapses to ~23 IPv4 prefixes (full list updates over time): 31.13.24.0/21 31.13.64.0/18 45.64.40.0/22 57.141.0.0/21 57.141.8.0/22 57.141.12.0/23 57.144.0.0/14 66.220.144.0/20 69.63.176.0/20 69.171.224.0/19 74.119.76.0/22 102.132.96.0/20 103.4.96.0/22 129.134.0.0/16 147.75.208.0/20 157.240.0.0/16 163.70.128.0/17 163.77.128.0/17 173.252.64.0/18 179.60.192.0/22 185.60.216.0/22 185.89.216.0/22 204.15.20.0/22 Generate the current list yourself: whois -h whois.radb.net -- '-i origin AS32934' | grep '^route' | awk '{print $2}' | sort Or use the geofeed CSV from Meta's webhooks documentation. Better: use **mTLS** for webhook authentication instead of IP allowlisting, since the IPs do change. Meta supports mTLS for webhooks (separate from SIP, which doesn't support mTLS). For SIP-mode setups, the same Meta IP space applies — your SIP server's firewall must allow inbound TLS from these prefixes. Related ------- * [Cloud API Calling](https://dualhook.com/docs/calling) Voice calling on WhatsApp Business Cloud API: VoIP via WebRTC or SIP, business-initiated and user-initiated calls, prerequisites, availability, and integration patterns. * [Business-Initiated Calls](https://dualhook.com/docs/calling-business-initiated) Place outbound voice calls to WhatsApp users. POST /calls with action: connect, SDP offer/answer flow, call status webhooks (RINGING/ACCEPTED/REJECTED), terminate webhook with duration, biz\_opaque\_callback\_data tracking. * [User-Initiated Calls](https://dualhook.com/docs/calling-user-initiated) Receive inbound voice calls from WhatsApp users. Call Connect webhook with SDP offer, pre-accept for faster setup, accept/reject/terminate actions, DTMF dial pad, companion-device support. * [Calling Troubleshooting](https://dualhook.com/docs/calling-troubleshooting) WhatsApp Calling errors and fixes: full error code table (138000-138023), SIP-specific status codes, media-flow timeouts, audio clipping causes and mitigations (SDES, pre-accept), quick triage guide. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Marketing Creative Optimizations | Dualhook Browse Docs Marketing Creative Optimizations ================================ Configure Meta's automatic creative optimizations on MM API marketing templates: creative\_features\_spec at template and WABA level, opt-in/opt-out per key. What Creative Optimizations Do[](https://dualhook.com/docs/marketing-creative-optimizations#what-creative-optimizations-do) ---------------------------------------------------------------------------------------------------------------------------- [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) can automatically apply visual and textual enhancements to marketing template messages to increase engagement. They are inspired by Meta's Advantage+ creative tooling for ads. Meta reports an average **+13.9% click-through rate** lift from the optimizations active as of January 2026 (A/B test on 50M+ messages from ~200 advertisers, December 1, 2025 – January 7, 2026, statistically significant at 95% confidence). These optimizations introduce **creative variability** — the same template can render differently across messages because Meta is testing variants and selecting the highest-performing one over time. They are exclusive to MM API; Cloud API marketing sends never have them applied. Active Optimizations[](https://dualhook.com/docs/marketing-creative-optimizations#active-optimizations) -------------------------------------------------------------------------------------------------------- | Key | What it does | | --- | --- | | `image_brightness_and_contrast` | Apply effective brightness/contrast filters to header images | | `image_touchups` | General visual quality enhancements | | `image_animation` | Convert header images to animated GIFs (in testing, may not always run) | | `image_background_gen` | Generate alternative image backgrounds | | `image_banner` | Apply colorful padding to transform image creative to optimal aspect ratio | | `text_extraction_for_headline` | Extract keywords/phrases from your message to create a headline highlighting key info | | `text_extraction_for_tap_target` | Extract keywords/phrases to create a title for the tap-target area | | `text_formatting_optimization` | Adjust text formatting (remove excess whitespace, bold key phrases). No content change | | `auto_promotion_tag` | Extract promotional phrases like "30% off" or "Free shipping" into a visual promotion tag on the image | | `product_extensions` | For single-image creatives, append related catalog products users are likely to engage with | | Dynamic CTA | Tailor CTA text to match the message or URL's value proposition | | Hyperlink formatting | Convert promotional keyphrases into hyperlinks mapped to the CTA, or shorten / format URL links in the message body | Paused or Deprecated[](https://dualhook.com/docs/marketing-creative-optimizations#paused-or-deprecated) -------------------------------------------------------------------------------------------------------- These have been paused and **should not be expected to apply** to current sends, even if the keys appear in older configs: * `add_text_overlay` — was: add a text overlay onto your image using the message content * Image cropping (no key) — was: auto-crop header images to optimal dimensions * Original `image_animation` was paused but is now back in testing under the same key Meta will update its documentation if any of these restart. Text Truncation (Always Applied)[](https://dualhook.com/docs/marketing-creative-optimizations#text-truncation-always-applied) ------------------------------------------------------------------------------------------------------------------------------ Independent of `creative_features_spec`, all MM API sends have automatic text truncation applied based on message shape. The truncated content stays accessible behind a "Read more" toggle. | Message shape | Truncated to | | --- | --- | | No CTA but link in body | 5 lines | | Media header (image, video, document, location, GIF) | 3 lines | | No header (text-only) | 4 lines | This rule cannot be opted out of. Where They Apply[](https://dualhook.com/docs/marketing-creative-optimizations#where-they-apply) ------------------------------------------------------------------------------------------------ * **Template-level config wins.** If a template has its own `creative_features_spec`, that overrides the WABA-level default for messages sent with that template. * **Default state at template level: enabled.** New templates have all optimizations on unless you explicitly opt out per key. * **Default state at WABA level: disabled.** WABA-level config requires explicit `OPT_IN` per key to apply. * Both default behaviors are intentional — templates lean into optimization out of the box, while account-wide opt-in stays a deliberate choice. Configure at the Template Level[](https://dualhook.com/docs/marketing-creative-optimizations#configure-at-the-template-level) ------------------------------------------------------------------------------------------------------------------------------ Set `creative_features_spec` inside `degrees_of_freedom_spec` when creating or updating the template: POST //message_templates { "name": "", "language": "", "components": [/* ... */], "degrees_of_freedom_spec": { "creative_features_spec": { "image_brightness_and_contrast": { "enroll_status": "OPT_OUT" }, "image_touchups": { "enroll_status": "OPT_IN" }, "image_animation": { "enroll_status": "OPT_IN" }, "image_background_gen": { "enroll_status": "OPT_IN" }, "auto_promotion_tag": { "enroll_status": "OPT_IN" }, "text_extraction_for_headline": { "enroll_status": "OPT_IN" }, "text_extraction_for_tap_target": { "enroll_status": "OPT_IN" }, "product_extensions": { "enroll_status": "OPT_OUT" }, "text_formatting_optimization": { "enroll_status": "OPT_OUT" } } } } Each key takes either `OPT_IN` or `OPT_OUT`. Keys you omit fall back to the template default (enabled). Configure at the WABA Level[](https://dualhook.com/docs/marketing-creative-optimizations#configure-at-the-waba-level) ---------------------------------------------------------------------------------------------------------------------- Apply a default across every marketing template under a WABA: POST / { "degrees_of_freedom_spec": { "creative_features_spec": { "image_touchups": { "enroll_status": "OPT_IN" }, "image_animation": { "enroll_status": "OPT_IN" }, "image_brightness_and_contrast": { "enroll_status": "OPT_IN" }, "image_background_gen": { "enroll_status": "OPT_IN" }, "auto_promotion_tag": { "enroll_status": "OPT_IN" }, "text_extraction_for_headline": { "enroll_status": "OPT_IN" }, "text_extraction_for_tap_target": { "enroll_status": "OPT_IN" }, "product_extensions": { "enroll_status": "OPT_IN" }, "text_formatting_optimization": { "enroll_status": "OPT_OUT" } } } } WABA-level keys you omit stay at the WABA default (disabled). A template that explicitly sets a key still wins over the WABA default. Retrieve Current Config[](https://dualhook.com/docs/marketing-creative-optimizations#retrieve-current-config) -------------------------------------------------------------------------------------------------------------- **Template:** GET /?fields=degrees_of_freedom_spec { "degrees_of_freedom_spec": { "creative_features_spec": [\ { "key": "IMAGE_BRIGHTNESS_AND_CONTRAST", "value": { "enroll_status": "OPT_OUT" } },\ { "key": "IMAGE_TOUCHUPS", "value": { "enroll_status": "OPT_OUT" } },\ { "key": "TEXT_EXTRACTION_FOR_HEADLINE", "value": { "enroll_status": "OPT_OUT" } },\ { "key": "PRODUCT_EXTENSIONS", "value": { "enroll_status": "OPT_IN" } },\ { "key": "TEXT_FORMATTING_OPTIMIZATION", "value": { "enroll_status": "OPT_IN" } }\ ] }, "id": "123456789" } Note the read shape uses upper-case keys and an array of `{key, value}` objects, while the write shape uses lower-case keys and an object map. **WABA:** GET /?fields=degrees_of_freedom_spec { "degrees_of_freedom_spec": { "data": [\ {\ "creative_features_spec": [\ {\ "image_brightness_and_contrast": "OPT_IN",\ "image_touchups": "OPT_IN",\ "image_animation": "OPT_IN",\ "image_background_gen": "OPT_IN",\ "auto_promotion_tag": "OPT_IN",\ "text_extraction_for_headline": "OPT_IN",\ "product_extensions": "OPT_IN",\ "text_extraction_for_tap_target": "OPT_IN",\ "text_formatting_optimization": "OPT_IN"\ }\ ]\ }\ ] }, "id": "1234567890" } The WABA read shape is yet another variant — flat key/value pairs inside `creative_features_spec[0]`. Plan your client code accordingly. Operational Notes[](https://dualhook.com/docs/marketing-creative-optimizations#operational-notes) -------------------------------------------------------------------------------------------------- * Meta is **continuously adding new optimizations**. New keys may be applied automatically to opted-in templates without explicit migration. Audit your `degrees_of_freedom_spec` periodically if you care about deterministic rendering. * Optimizations only apply on MM API sends. The same template sent through Cloud API `/messages` will render verbatim with no enhancements. * The same template can render differently across messages — this is the design, not a bug. If your QA process expects pixel-identical sends, MM API is the wrong send path. * All optimizations preserve message content. Text formatting and truncation re-flow but do not alter the words. Related ------- * [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) Reference for Meta's separate /marketing\_messages product, which is not exposed by Dualhook's runtime or Embedded Signup configuration. * [Marketing Messages](https://dualhook.com/docs/marketing-messages) Marketing template messaging through Dualhook's standard Cloud API runtime, plus boundaries for Meta's separate MM API product. * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Messaging Health Status | Dualhook Browse Docs Messaging Health Status ======================= Monitor phone number health: AVAILABLE, LIMITED, BLOCKED states and quality rating. Overview[](https://dualhook.com/docs/health-monitoring#overview) ----------------------------------------------------------------- Messaging health status indicates whether a [phone number](https://dualhook.com/docs/phone-numbers) can currently send WhatsApp messages. Dualhook displays this as one of four states: * `AVAILABLE` — normal operations * `LIMITED` — degraded quality, reduced capacity * `BLOCKED` — messaging suspended * `UNKNOWN` — health data not yet retrieved Dualhook pulls health from Meta Graph API and stores history snapshots for monitoring dashboards. In coexistence / Embedded Signup setups, not every raw `LIMITED` response means quality or policy trouble. If the only Meta notice is the display-name approval notice and display-name statuses are `name_status = NON_EXISTS` plus `new_name_status = PENDING_REVIEW`, Dualhook treats that as a separate expected coexistence state in customer-facing UI while preserving Meta's raw `LIMITED` status in diagnostics. The connection **Debug** tab exposes the raw Meta diagnostics behind these signals, with inline guidance for common expected patterns such as coexistence display-name gating and webhook-routing checks. Graph API Health Check[](https://dualhook.com/docs/health-monitoring#graph-api-health-check) --------------------------------------------------------------------------------------------- Treat management webhooks as the real-time source. Use this read only to reconcile state, at most once every five minutes. Dualhook caches each successful field set for 60 seconds, and the phone's `health_status` already includes the related WABA, business, and app health summary. Query the connection's phone-number node through the Runtime API: curl -X GET "https://api.dualhook.com/v25.0/?fields=health_status,quality_rating,account_mode,whatsapp_business_manager_messaging_limit" \ -H "Authorization: Bearer " Do not submit the Meta token or `appsecret_proof`; Dualhook applies proof internally and verifies the phone ID against the selected connection. ### AVAILABLE Response[](https://dualhook.com/docs/health-monitoring#available-response) { "id": "123456789012345", "health_status": { "can_send_message": "AVAILABLE", "entities": [] }, "quality_rating": "GREEN", "account_mode": "LIVE", "whatsapp_business_manager_messaging_limit": "TIER_10K" } ### LIMITED Response[](https://dualhook.com/docs/health-monitoring#limited-response) { "id": "123456789012345", "health_status": { "can_send_message": "LIMITED", "entities": [\ {\ "entity_type": "PHONE_NUMBER",\ "id": "123456789012345",\ "can_send_message": "LIMITED",\ "additional_info": [\ "Recent quality degradation detected."\ ],\ "errors": [\ {\ "error_code": 131056,\ "error_description": "Rate limit reached for current quality tier.",\ "possible_solution": "Improve quality and reduce promotional send volume."\ }\ ]\ }\ ] }, "quality_rating": "YELLOW", "account_mode": "LIVE", "whatsapp_business_manager_messaging_limit": "TIER_2K" } ### BLOCKED Response[](https://dualhook.com/docs/health-monitoring#blocked-response) { "id": "123456789012345", "health_status": { "can_send_message": "BLOCKED", "entities": [\ {\ "entity_type": "PHONE_NUMBER",\ "id": "123456789012345",\ "can_send_message": "BLOCKED",\ "additional_info": [\ "Messaging blocked due to policy enforcement."\ ]\ }\ ] }, "quality_rating": "RED", "account_mode": "LIVE", "whatsapp_business_manager_messaging_limit": "TIER_250" } Health Status States[](https://dualhook.com/docs/health-monitoring#health-status-states) ----------------------------------------------------------------------------------------- | State | Meaning | Action | | --- | --- | --- | | `AVAILABLE` | Normal messaging operations | Continue monitoring | | `LIMITED` | Quality degradation detected | Reduce volume, improve targeting | | `BLOCKED` | Messaging suspended by Meta | Pause all sends, remediate immediately | | `UNKNOWN` | No health data available yet | Trigger a manual health check | The table above describes Meta's raw health states. Your product UI may still want a friendlier derived state for known expected patterns such as coexistence display-name gating. Tier Field Notes[](https://dualhook.com/docs/health-monitoring#tier-field-notes) --------------------------------------------------------------------------------- Request the current portfolio-level limit field: * `whatsapp_business_manager_messaging_limit` — shared business-portfolio messaging limit Dualhook uses this field for new health checks. For background on the portfolio-level shift, see [Capacity, Quality & Messaging Limits](https://dualhook.com/docs/capacity-quality-limits) . Health Webhook Events[](https://dualhook.com/docs/health-monitoring#health-webhook-events) ------------------------------------------------------------------------------------------- Health changes are also reflected via management webhook events. See [Webhook Events](https://dualhook.com/docs/webhook-events) for the full event list. ### phone\_number\_quality\_update[](https://dualhook.com/docs/health-monitoring#phone_number_quality_update) { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "phone_number_quality_update",\ "value": {\ "event": "QUALITY_UPDATED",\ "phone_number_id": "123456789012345",\ "display_phone_number": "15550001234",\ "current_limit": "TIER_2K",\ "current_quality_rating": "YELLOW",\ "previous_quality_rating": "GREEN"\ }\ }\ ]\ }\ ] } ### account\_update[](https://dualhook.com/docs/health-monitoring#account_update) { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "account_update",\ "value": {\ "event": "VERIFIED_ACCOUNT",\ "ban_info": {\ "waba_ban_state": "NONE"\ }\ }\ }\ ]\ }\ ] } Recommended Ops Flow[](https://dualhook.com/docs/health-monitoring#recommended-ops-flow) ----------------------------------------------------------------------------------------- 1. Process health-related webhook events in real time. 2. Reconcile with a phone health read at most once every five minutes. 3. Alert on `LIMITED` and `BLOCKED` states. 4. Review quality trends, tier movement, and alert causes before scaling campaigns. Dualhook surfaces health snapshots and quality-change events in the monitoring dashboard. For a complete quality management strategy, see [Maintain Account Health](https://dualhook.com/docs/account-health) . Related ------- * [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) API rate limits, messaging limit tiers, quality rating, and scaling guidance. * [Maintain Account Health](https://dualhook.com/docs/account-health) Ownership hygiene, quality monitoring, opt-out governance, and incident playbook. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp OTP Android SDK: Handshake, Eligibility, Migration | Dualhook Browse Docs OTP Handshake & Android SDK =========================== How the WhatsApp OTP handshake works for one-tap and zero-tap authentication templates: OTP Android SDK, manifest setup, eligibility checks, multi-app support, error codes, and PendingIntent deprecation. What the Handshake Does[](https://dualhook.com/docs/authentication-otp-handshake#what-the-handshake-does) ---------------------------------------------------------------------------------------------------------- A handshake is an Android intent your app sends to the WhatsApp client (or WhatsApp Business app) before you call the [authentication template send endpoint](https://dualhook.com/docs/authentication-messages#sending-an-authentication-template) . The handshake tells WhatsApp to expect imminent delivery of a one-time password and authorizes WhatsApp to deliver the OTP back to your app via intent (one-tap) or broadcast (zero-tap). Without a successful handshake, WhatsApp falls back to displaying a copy-code button — even if your template is configured for one-tap or zero-tap. When You Need It[](https://dualhook.com/docs/authentication-otp-handshake#when-you-need-it) -------------------------------------------------------------------------------------------- | Authentication template type | Handshake required? | | --- | --- | | `copy_code` | No | | `one_tap` | Yes | | `zero_tap` | Yes | The handshake is **per-OTP-request**, not per-session. Initiate it every time the user requests a code. The OTP Android SDK (Preferred)[](https://dualhook.com/docs/authentication-otp-handshake#the-otp-android-sdk-preferred) ------------------------------------------------------------------------------------------------------------------------ Meta provides an Android SDK that wraps the handshake protocol, generates the request\_id (UUID), validates incoming OTPs, and maps low-level errors to typed error codes. **It is the preferred and only supported method going forward** — see [PendingIntent Deprecation](https://dualhook.com/docs/authentication-otp-handshake#pendingintent-deprecation-oct-15-2026) below. Add to your Gradle file: dependencies { implementation 'com.whatsapp.otp:whatsapp-otp-android-sdk:1.0.0' } repositories { mavenCentral() } Manifest Setup[](https://dualhook.com/docs/authentication-otp-handshake#manifest-setup) ---------------------------------------------------------------------------------------- Declare a query for the WhatsApp packages so Android allows your app to detect and broadcast to them: For one-tap, declare an Activity that will receive the OTP intent: For zero-tap, declare a BroadcastReceiver instead: If your template's eligibility-failure fallback can drop from zero-tap to one-tap (which it does by default), declare **both** — the broadcast receiver for zero-tap and the activity for the one-tap fallback. Initiating the Handshake[](https://dualhook.com/docs/authentication-otp-handshake#initiating-the-handshake) ------------------------------------------------------------------------------------------------------------ The SDK generates a UUID handshake ID, broadcasts the OTP\_REQUESTED intent to both `com.whatsapp` and `com.whatsapp.w4b` (Business), and returns the UUID for you to store and validate against the incoming OTP later. WhatsAppOtpHandler whatsAppOtpHandler = new WhatsAppOtpHandler(); UUID handshakeId = whatsAppOtpHandler.sendOtpIntentToWhatsApp(context); // Persist handshakeId so the receiver can validate it when the OTP arrives After this returns, call your backend to send the authentication template via the WhatsApp Cloud API. The handshake remains valid for 10 minutes (or `code_expiration_minutes` if set lower on the template). Receiving the OTP (One-Tap)[](https://dualhook.com/docs/authentication-otp-handshake#receiving-the-otp-one-tap) ---------------------------------------------------------------------------------------------------------------- In the Activity declared in the manifest, instantiate `WhatsAppOtpIncomingIntentHandler`, pass the stored handshake ID, and process the intent: public class ReceiveCodeActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); WhatsAppOtpIncomingIntentHandler handler = new WhatsAppOtpIncomingIntentHandler(); String expectedHandshakeId = retrieveStoredHandshakeId(); handler.processOtpCode( getIntent(), expectedHandshakeId, (code) -> { // SDK validated the handshake ID — code is trusted validateCode(code); }, (error, exception) -> handleError(error, exception) ); } } `processOtpCode` validates the incoming `request_id` against your stored handshake ID. If they match, the success callback receives the code. If they don't (or any other validation fails), the error callback receives a typed `HANDSHAKE_ID_*` error. Receiving the OTP (Zero-Tap)[](https://dualhook.com/docs/authentication-otp-handshake#receiving-the-otp-zero-tap) ------------------------------------------------------------------------------------------------------------------ For zero-tap, the BroadcastReceiver follows the same pattern: public class OtpCodeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { WhatsAppOtpIncomingIntentHandler handler = new WhatsAppOtpIncomingIntentHandler(); String expectedHandshakeId = retrieveStoredHandshakeId(); handler.processOtpCode( intent, expectedHandshakeId, (code) -> validateCode(code), (error, exception) -> handleError(error, exception) ); } } The user does not see anything — your app receives the broadcast and processes the OTP silently. Eligibility Checks[](https://dualhook.com/docs/authentication-otp-handshake#eligibility-checks) ------------------------------------------------------------------------------------------------ When WhatsApp receives an authentication template message, it runs an eligibility check before deciding whether to honor the configured button type. If any check fails, the message falls back: zero-tap → one-tap → copy-code. Checks performed (per Meta): 1. The handshake was initiated no more than **10 minutes ago** (or `code_expiration_minutes` if shorter). 2. The package name in the message (`package_name` on the template) matches the package name set on the intent (verified via `getCreatorPackage` on the PendingIntent provided by your app, where applicable). 3. None of the other apps in the template's `supported_apps` array initiated a handshake in the last 10 minutes. 4. The app signing key hash in the message (`signature_hash`) matches the installed app's signing key hash. 5. The message includes the autofill button text. 6. Your app has declared an Activity (one-tap) or BroadcastReceiver (zero-tap) that handles `com.whatsapp.otp.OTP_RETRIEVED`. If any of these fail, you'll see the message rendered with a copy-code button instead of the configured one-tap or zero-tap button. Multi-App Support (supported\_apps)[](https://dualhook.com/docs/authentication-otp-handshake#multi-app-support-supported_apps) ------------------------------------------------------------------------------------------------------------------------------- The `supported_apps` array on an authentication template's button accepts up to **5** package\_name + signature\_hash pairs. Useful when you have multiple app builds (debug, staging, production) or distribute your app under different package names per platform. "supported_apps": [\ { "package_name": "com.example.app", "signature_hash": "K8a/AINcGX7" },\ { "package_name": "com.example.app.debug", "signature_hash": "Lp9/BJOdHY8" }\ ] Eligibility check passes if **any** of the listed pairs matches the installed app initiating the handshake. Only one of the listed apps can have an active handshake at a time (per check #3 above). App Signing Key Hash[](https://dualhook.com/docs/authentication-otp-handshake#app-signing-key-hash) ---------------------------------------------------------------------------------------------------- To compute your app signing key hash, follow Google's [Computing Your App's Hash String](https://developers.google.com/identity/sms-retriever/verify) guide. Alternatively, after downloading your signing key certificate, run Meta's helper script: ./sms_retriever_hash_v9.sh --package "com.example.myapplication" --keystore ~/.android/debug.keystore The hash is exactly **11 characters** and contains only `a-z`, `A-Z`, `0-9`, `+`, `/`, or `=`. Android Notifications[](https://dualhook.com/docs/authentication-otp-handshake#android-notifications) ------------------------------------------------------------------------------------------------------ Android only shows a system notification for an incoming WhatsApp authentication template message if all of these are true: * The user is logged into WhatsApp (or WhatsApp Business) with the same phone number the message was sent to. * The user is logged into your app. * Android OS is KitKat (4.4, API 19) or above. * "Show notifications" is enabled in WhatsApp's settings. * Device-level notifications are enabled for WhatsApp. * The prior message thread between the user and your business is not muted. Notification absence does **not** mean delivery failure — the OTP still arrives via intent or broadcast even with notifications suppressed. Error Codes[](https://dualhook.com/docs/authentication-otp-handshake#error-codes) ---------------------------------------------------------------------------------- When using the SDK with handshake ID validation, these error codes can be returned: | Code | Meaning | | --- | --- | | `HANDSHAKE_ID_MISSING` | The handshake ID was not included in the intent from WhatsApp | | `HANDSHAKE_ID_INVALID_FORMAT` | The handshake ID is not a valid UUID format | | `HANDSHAKE_ID_MISMATCH` | The handshake ID in the intent does not match the expected (stored) value | A `HANDSHAKE_ID_MISMATCH` error is the most security-relevant — it can indicate the OTP was delivered for a request initiated by another instance of your app (or a different process), or that an attacker is attempting to inject an OTP. Never accept the code on mismatch. PendingIntent Deprecation (Oct 15, 2026)[](https://dualhook.com/docs/authentication-otp-handshake#pendingintent-deprecation-oct-15-2026) ----------------------------------------------------------------------------------------------------------------------------------------- The PendingIntent-based handshake method (used in older code samples) will be deprecated on **October 15, 2026** (extended from the earlier deadline). The legacy approach used `PendingIntent.getActivity` to attach a caller identity to the OTP\_REQUESTED intent so WhatsApp could verify the source via `getCreatorPackage`. This works today but stops working after the deadline. **Migrate to the OTP Android SDK** — `WhatsAppOtpHandler.sendOtpIntentToWhatsApp(context)` returns a UUID handshake ID and replaces all the PendingIntent boilerplate. The SDK is forward-compatible and is the only supported handshake method post-deprecation. If you cannot use the SDK for some reason, the manual replacement uses an explicit `request_id` (UUID) extra on the OTP\_REQUESTED broadcast and validates the same `request_id` on the receiving side. But there is no good reason to avoid the SDK in new code. iOS and Other Platforms[](https://dualhook.com/docs/authentication-otp-handshake#ios-and-other-platforms) ---------------------------------------------------------------------------------------------------------- One-tap and zero-tap are **Android-only**. Authentication templates sent to users on iOS or any other platform always render with a copy-code button regardless of how you configured the template. To check WhatsApp installation on iOS: let schemeURL = URL(string: "whatsapp://otp")! let isWhatsAppInstalled = UIApplication.shared.canOpenURL(schemeURL) For copy-code on any platform, no handshake is required and no app-side integration is needed beyond reading the pasted value. Related ------- * [Authentication Messages](https://dualhook.com/docs/authentication-messages) OTP and verification code delivery via WhatsApp: copy-code, one-tap, and zero-tap templates, linked device security, the 'I didn't request a code' webhook, and PendingIntent deprecation. * [Authentication Best Practices](https://dualhook.com/docs/authentication-best-practices) Security and UX best practices for WhatsApp authentication: identity hash binding for recycled phone protection, opt-in collection, isWhatsAppInstalled checks for Android and iOS, multi-app support. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Embedded Signup Error Codes and Fixes | Dualhook Browse Docs Embedded Signup Errors ====================== Searchable Meta Embedded Signup error index with safe first actions for business, WABA, phone, OTP, sharing, and account restrictions. This is a searchable, action-oriented index of errors Meta can show during WhatsApp Embedded Signup. Wording can vary by flow version and locale. How to Use This Index[](https://dualhook.com/docs/embedded-signup-errors#how-to-use-this-index) ------------------------------------------------------------------------------------------------ Search this page for the numeric code or a distinctive phrase from Meta's dialog. Follow the first safe action before deleting or recreating any business asset. Errors displayed inside Meta's popup occur before Dualhook can inspect or change the selected asset. Generic textual errors have these usual first steps: | Message or situation | First safe action | | --- | --- | | Request failed or “Something has gone wrong” | Wait a few minutes and retry once. If it persists, capture the session ID and contact Meta Business Support. | | Business creation limit or “one business user at a time” | Select an existing Business Portfolio or wait for Meta's creation limit to clear. | | Facebook account too new, spam-limited, advertising-disabled, or payment-disabled | Use an established authorized admin account and resolve its restriction in Account Quality/Business Support. | | Invalid business name | Use the real business or brand name and follow Meta's naming rules. | | No permission to create a WABA | Obtain full admin access to the selected Business Portfolio. | | WABA creation limited until verification | Complete [Meta Business Verification](https://dualhook.com/docs/meta-business-verification)
or use an eligible existing WABA. | | Phone blocked or account cannot be verified | Review Account Quality and Business Support before retrying. | | Phone already exists in the selected WABA | Restart the flow and choose the existing number instead of adding it again. | | Existing WhatsApp registration | Confirm whether you are using Coexistence or migration before deregistering anything. See [Phone Numbers](https://dualhook.com/docs/phone-numbers)
. | | Verified/display name rejected or under review | Wait for the current review or correct the name using [Display Names](https://dualhook.com/docs/display-names)
. | | OTP guessed/requested too many times | Stop retrying and wait for the period shown by Meta; repeated attempts extend rate limits. | What Dualhook Shows[](https://dualhook.com/docs/embedded-signup-errors#what-dualhook-shows) -------------------------------------------------------------------------------------------- When Meta returns a documented numeric error to the browser, Dualhook shows a safe explanation and the first recommended action. The notice links back to this index so you can search the exact code. A Meta `CANCEL` event is shown as a cancellation and identifies the screen where the flow stopped; it is not mislabeled as a blocked popup. For operational reconciliation and Meta support, Dualhook records only the numeric Meta error ID, Meta session ID, Meta error timestamp, and the documented `current_step` value. It does not store the raw Embedded Signup event, OAuth authorization code, Meta error text, or selected-business payload. The localized Meta message may be shown once in the browser, but is not persisted. Unknown codes remain visible by number instead of being guessed. Do not repeatedly relaunch Embedded Signup after an eligibility, restriction, ownership, or rate-limit error. Those conditions generally require an account-side correction or a waiting period, and repeated attempts can extend Meta's limits. Abandoned Flow Screens[](https://dualhook.com/docs/embedded-signup-errors#abandoned-flow-screens) -------------------------------------------------------------------------------------------------- Dualhook records the `CANCEL` screen name for attributable attempts. These values are not failures by themselves: | Current step | Screen | | --- | --- | | `BUSINESS_ACCOUNT_SELECTION` | Business Portfolio selection | | `WABA_PHONE_PROFILE_PICKER` | WABA/phone selection | | `WHATSAPP_BUSINESS_PROFILE_SETUP` | WABA or business-profile creation | | `PHONE_NUMBER_SETUP` | Phone-number addition | | `PHONE_NUMBER_VERIFICATION` | OTP verification | | `PERMISSIONS` | Permissions review | Business Portfolio and WABA[](https://dualhook.com/docs/embedded-signup-errors#business-portfolio-and-waba) ------------------------------------------------------------------------------------------------------------ | Codes | Meaning | First safe action | | --- | --- | --- | | `2655081`, `1752140`, `2455008` | Ad-account limit reached | Check Business Info, billing, and business verification; reuse an eligible existing ad account. | | `2655055`, `2494149` | Ad-account link or creation failed | Retry once; escalate if the same session repeatedly fails. | | `1752004`, `3441056`, `2446325` | Ad account/business is invalid, violates policy, or is under review | Resolve the listed issue in Business Support Home. | | `1690214` | Payment account disabled | Ask the financial admin to resolve or appeal the disabled payment account. | | `2388098` | More WABAs require verification | Complete business verification before creating another WABA. | | `1752089` | Only one Business account can be created at a time | Wait or select an existing Business Portfolio. | | `2494067`, `1690114` | WABA/Business creation limit reached | Reuse an existing eligible asset; delete one only after confirming it is genuinely unused. | | `1690089`, `1690091`, `1690062`, `1690090` | Duplicate or invalid business/system-user name | Choose a distinct valid real-world name. | | `2593010` | WABA name exceeds 255 characters | Shorten the WABA name. | | `3771014` | Invalid link | Correct the URL. | | `2388010`, `2388009` | Verified name cannot be edited | Read the detailed Meta reason and wait for any active name review. | | `2655091` | CAPTCHA incomplete | Complete the CAPTCHA in the Meta popup. | | `2494030` | Business verification is still in progress | Check Security Center and wait for the review result. | | `2494143` | Another partner already started Partner-led Business Verification | Continue with that partner or use standard Meta Business Verification. | See [WhatsApp Business Accounts](https://dualhook.com/docs/whatsapp-business-accounts) , [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) , and [Meta Business Verification](https://dualhook.com/docs/meta-business-verification) . Phone and Coexistence[](https://dualhook.com/docs/embedded-signup-errors#phone-and-coexistence) ------------------------------------------------------------------------------------------------ | Codes | Meaning | First safe action | | --- | --- | --- | | `3441041` | Number belongs to a different selected business | Select the correct business/number. Do not delete it until ownership is confirmed. | | `3441045` | Number is not yet eligible for Business Platform Coexistence | Continue legitimate WhatsApp Business app activity and retry when Meta marks it eligible. | | `2655095`, `2655048` | Number is not registered with WhatsApp Business | Register it in WhatsApp Business or use an already registered Business number. | | `3441042` | Region is not supported for this feature | Use a supported number/region; Dualhook cannot override regional eligibility. | | `2655093` | Account is already shared with a partner | Follow [Error #2655093: Already Shared With a Partner](https://dualhook.com/docs/whatsapp-error-2655093)
. | | `2655082` | Number/account is linked to a conflicting Facebook Page | Confirm the correct Page/business ownership before removing that Page relationship. | | `3441049`, `2655094` | WhatsApp Business still has a Business Platform connection | Disconnect it in the app, wait at least 15 minutes, then retry. | | `3441030` | Number is already registered elsewhere | Decide whether this should be Coexistence or migration before deregistering. | | `3441047` | Account using Marketing Messages is not eligible for this flow | Select another eligible WABA or resolve the incompatible product enrollment. | | `4563015` | WhatsApp Business app is outdated | Update the app before retrying. | | `3441051` | Invalid phone number | Verify country code and number, then try a working carrier-issued number. | | `2655011` | Number is not linked to a WhatsApp Business account | Link/register the correct number or choose another one. | | `2388002` | Meta could not add the number | Use the detailed sub-error; check ownership, limits, and account restrictions. | | `2655076` | Virtual/test number creation is blocked | Check WABA restrictions in Business Support Home. | | `2655005` | WhatsApp numbers temporarily unavailable | Retry later. | Verification and Rate Limits[](https://dualhook.com/docs/embedded-signup-errors#verification-and-rate-limits) -------------------------------------------------------------------------------------------------------------- | Codes | Meaning | First safe action | | --- | --- | --- | | `2593037`, `2388091` | Verification code could not be sent | Confirm SMS/voice availability and the number format; wait before requesting another code. | | `2593039`, `2388093` | Verification code could not be verified | Re-enter the current code once; if it fails, wait for Meta's retry window. | | `2494158` | Too many OTP requests | Stop requesting codes and wait for the limit to clear. | | `3441033`, `3441046`, `1675004` | Too many signup/API attempts | Pause and retry later instead of repeatedly reopening the flow. | | `1390006`, `1390008` | Meta anti-abuse or feature-rate limit | Slow down and resolve any Facebook account restriction before retrying. | Sharing, System, and Account Status[](https://dualhook.com/docs/embedded-signup-errors#sharing-system-and-account-status) -------------------------------------------------------------------------------------------------------------------------- | Codes | Meaning | First safe action | | --- | --- | --- | | `2655049`, `2494028`, `2494119` | WABA already shared/duplicate provider | Use the [#2655093 partner-release guide](https://dualhook.com/docs/whatsapp-error-2655093)
. | | `2494029`, `2494120` | Account cannot currently be shared | Capture the session ID and contact Meta Business Support. | | `2593072` | Selected WABA does not exist or is inaccessible | Select an accessible WABA and verify admin permissions. | | `2494091` | Solution Partner has not completed verification | The provider must finish its own business verification. | | `1675030`, `1675012` | Temporary Meta query/request failure | Retry later. | | `2593030`, `3441003`, `4056009` | Generic account/setup failure | Retry once, then contact Meta with the session ID. | | `2593035` | Phone creation failed | Recheck the number; escalate if the same valid number keeps failing. | | `1357053` | Account restricted from this action | Review Account Quality and resolve the restriction. | | `1752224` | A previous business-relationship creation is still running | Wait for the prior attempt to finish before retrying. | | `1752206` | User cannot be added to the business | Verify the user's admin access or contact Meta. | | `1690061` | Asset already belongs to this Business Portfolio | Use the existing asset instead of claiming it again. | | `1349246` | Requested asset permissions were not granted | Retry with the correct business admin and grant the requested assets. | | `2655069` | Marketing Messages terms acceptance failed | Retry the terms step; contact Meta if it persists. | | `2494102` | Invalid uploaded-media handle | Recreate or correct the referenced media handle. | | `2655089`, `3441040`, `2388107` | WABA is restricted | Resolve the WABA issue in Account Quality/Business Support before adding a number. | | `2859015` | Temporarily blocked | Wait and review Business Support restrictions. | | `1784028`, `1404163` | Business/advertising access prohibited | Resolve or appeal the enforcement in Business Support Home. | See [Maintain Account Health](https://dualhook.com/docs/account-health) before retrying restricted accounts. Authentication and Pages[](https://dualhook.com/docs/embedded-signup-errors#authentication-and-pages) ------------------------------------------------------------------------------------------------------ | Codes | Meaning | First safe action | | --- | --- | --- | | `1690181` | User is not a direct Page admin | Ask a direct Page admin to complete the action or grant the required role. | | `1752069` | New Page admin is inside Meta's security waiting period | Wait until the one-week restriction clears. | | `2859009`, `4612001` | Facebook profile/business requires two-factor authentication | Enable 2FA in Business Settings or Accounts Center, then reopen signup. | | `2859043` | New device/location verification required | Complete Meta's security verification from Business Home. | | `2136001` | Security challenge required | Complete the challenge in Meta before continuing. | Still Stuck[](https://dualhook.com/docs/embedded-signup-errors#still-stuck) ---------------------------------------------------------------------------- Do not repeatedly delete/recreate assets. Capture the exact code, Meta session/error ID, timestamp, selected Business ID/WABA ID, and screenshots. Then use [Support, Contact, and Bug Reports](https://dualhook.com/docs/contact-support) . If Meta finished but no Dualhook connection appeared, follow [Connection Not Appearing](https://dualhook.com/docs/connection-not-appearing) . Related ------- * [Embedded Signup](https://dualhook.com/docs/embedded-signup) How Dualhook uses Meta's Embedded Signup flow to connect WhatsApp Business Accounts via OAuth. * [Connection Not Appearing After Embedded Signup](https://dualhook.com/docs/connection-not-appearing) Why Meta can finish linking while no Dualhook connection appears, and how to retry the browser handoff safely. * [WhatsApp Error 2655093: Account Already Shared With a Partner](https://dualhook.com/docs/whatsapp-error-2655093) How to safely release an old BSP or Coexistence partner relationship when Meta blocks Embedded Signup with error 2655093. * [Troubleshooting](https://dualhook.com/docs/troubleshooting) Common issues and solutions for Dualhook connections, webhooks, and health monitoring. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Service Messages: CSW, Types, TTL, and Pricing | Dualhook Browse Docs Service Messages ================ Free-form messages inside an open customer service window: types, replies, TTL, and October 2026 pricing with 1,000 free messages per number each month. What Service Messages Are[](https://dualhook.com/docs/service-messages#what-service-messages-are) -------------------------------------------------------------------------------------------------- Service messages are **free-form** messages sent through the Cloud API `/messages` endpoint. They are the counterpart to template messages: where templates are pre-approved formats sent in any window, service messages are composed on the fly and only sendable inside an open customer service window (CSW). | | Service messages | Template messages | | --- | --- | --- | | Pre-approval required? | No | Yes | | When can you send? | Inside an open 24-hour CSW only | Any time, with a paid send if outside CSW | | Composition | Free-form | Bound to approved template structure | | Categories | Service (or `free_customer_service` for utility templates inside CSW) | `MARKETING` / `UTILITY` / `AUTHENTICATION` | | Pricing object `category` | `service` | `marketing`, `utility`, `authentication`, etc. | Use service messages to respond conversationally inside an active customer interaction. To message a user outside any CSW, you must use [marketing](https://dualhook.com/docs/marketing-messages) , [utility](https://dualhook.com/docs/utility-messages) , or [authentication](https://dualhook.com/docs/authentication-messages) templates. Service Pages in This Section[](https://dualhook.com/docs/service-messages#service-pages-in-this-section) ---------------------------------------------------------------------------------------------------------- * [Interactive Service Messages](https://dualhook.com/docs/service-messages-interactive) — CTA URL button, list, media carousel, reply buttons, location request * [Address Messages (India)](https://dualhook.com/docs/service-messages-address) — interactive form to collect a shipping address from Indian recipients * [Reaction Messages](https://dualhook.com/docs/service-messages-reactions) — emoji reactions on a previous user message * [Media Messages](https://dualhook.com/docs/media-messages) — image, video, audio, document, sticker * [Contacts & Location Messages](https://dualhook.com/docs/contacts-location) — share contact cards or location coordinates The Customer Service Window[](https://dualhook.com/docs/service-messages#the-customer-service-window) ------------------------------------------------------------------------------------------------------ When a WhatsApp user messages or calls you, a **24-hour timer** (the customer service window or CSW) starts. While that window is open, you can send any service message type listed below. Each new inbound message or call from the user resets the timer to 24 hours. When the window closes, you can only send pre-approved [template messages](https://dualhook.com/docs/templates) . You may only send messages to WhatsApp users who have opted in to receiving messages from you — the open window is a permission to respond, not a license to broadcast. In rare cases, an inbound message from a user may not open the CSW for you to respond. This is a known platform-level edge case. Message Types[](https://dualhook.com/docs/service-messages#message-types) -------------------------------------------------------------------------- The service-message types you can send during an open CSW: | Type | What it is | Where documented | | --- | --- | --- | | `text` | Text body with optional link preview | [Messaging Overview](https://dualhook.com/docs/messaging-overview) | | `image` | Single image with optional caption | [Media Messages](https://dualhook.com/docs/media-messages) | | `video` | Video with optional caption | [Media Messages](https://dualhook.com/docs/media-messages) | | `audio` | Voice message or basic audio | [Media Messages](https://dualhook.com/docs/media-messages) | | `document` | File with download icon | [Media Messages](https://dualhook.com/docs/media-messages) | | `sticker` | Static or animated WebP sticker | [Media Messages](https://dualhook.com/docs/media-messages) | | `location` | Location coordinates | [Contacts & Location](https://dualhook.com/docs/contacts-location) | | `contacts` | Contact card with up to 257 contacts | [Contacts & Location](https://dualhook.com/docs/contacts-location) | | `reaction` | Emoji reaction on a previous user message | [Reactions](https://dualhook.com/docs/service-messages-reactions) | | `interactive` (`cta_url`) | URL mapped to a button | [Interactive Service Messages](https://dualhook.com/docs/service-messages-interactive) | | `interactive` (`list`) | List of options in a modal | [Interactive Service Messages](https://dualhook.com/docs/service-messages-interactive) | | `interactive` (`button`) | Up to 3 reply buttons | [Interactive Service Messages](https://dualhook.com/docs/service-messages-interactive) | | `interactive` (`carousel`) | Horizontally scrollable media cards | [Interactive Service Messages](https://dualhook.com/docs/service-messages-interactive) | | `interactive` (`location_request_message`) | Send-location button | [Interactive Service Messages](https://dualhook.com/docs/service-messages-interactive) | | `interactive` (`address_message`) | Address-collection form (India only) | [Address Messages](https://dualhook.com/docs/service-messages-address) | | `interactive` (`flow`) | Structured Flow message | Meta WhatsApp Flows docs | Core Endpoint[](https://dualhook.com/docs/service-messages#core-endpoint) -------------------------------------------------------------------------- All service messages use the same endpoint: POST https://api.dualhook.com/v25.0//messages The body shape is consistent across types — the `type` field selects the message kind and the matching property carries the contents: { "messaging_product": "whatsapp", "recipient_type": "individual", "to": "12015550123", "type": "", "": { /* contents */ } } `recipient_type` is `individual` for 1-to-1 messages or `group` for group messaging (see Meta's Groups API docs). A successful response returns the WhatsApp message ID immediately, but **does not confirm delivery** — delivery status arrives asynchronously via a [status webhook](https://dualhook.com/docs/messaging-webhook) . For full status-lifecycle behavior, see [Messaging Overview](https://dualhook.com/docs/messaging-overview#delivery-status-lifecycle) . WhatsApp User Phone Number Formats[](https://dualhook.com/docs/service-messages#whatsapp-user-phone-number-formats) -------------------------------------------------------------------------------------------------------------------- Plus signs (`+`), hyphens, parentheses, and spaces are all permitted in the `to` field. Strongly include both the `+` and country calling code — without them, your business phone number's own country code may be prepended, sending the message to the wrong recipient. | Sent | Delivered to | Outcome (business in India, country code 91) | | --- | --- | --- | | `+16315551234` | `+16315551234` | Correct | | `+1 (631) 555-1234` | `+16315551234` | Correct | | `(631) 555-1234` | `+916315551234` | Wrong number — country code prepended | | `1 (631) 555-1234` | `+9116315551234` | Wrong number — country code prepended | For Brazil and Mexico, Cloud API may modify the prefix automatically — this is by design, not a bug. Contextual Replies[](https://dualhook.com/docs/service-messages#contextual-replies) ------------------------------------------------------------------------------------ You can send a service message as a contextual reply to a specific previous message. WhatsApp displays it with a quote bubble pointing to the message you're replying to. Pass the original message's `wamid` in the `context` object on your send call: { "messaging_product": "whatsapp", "to": "12015550123", "context": { "message_id": "wamid.HBgLM..." }, "type": "text", "text": { "body": "Yes, that order ships tomorrow." } } This makes it easier for the user to know which specific message you are replying to — useful in long, multi-thread conversations. Media Caching (Hosted URLs)[](https://dualhook.com/docs/service-messages#media-caching-hosted-urls) ---------------------------------------------------------------------------------------------------- When you send media via the `link` property (a URL on your public server) instead of `id` (an uploaded media asset), Cloud API fetches the asset and **caches it for 10 minutes**. Subsequent sends with the **same** URL within that window reuse the cached copy. To bypass the cache (e.g. you re-uploaded under the same path), append a random query string: First send: https://link.to.media/sample.jpg → fetched, cached 10 min Second send: https://link.to.media/sample.jpg → cached copy reused Third send: https://link.to.media/sample.jpg?abc123 → fetched, cached 10 min This is also why Meta recommends `id` over `link` for production sends — uploaded media has a stable identifier and avoids the caching footgun entirely. See [Media Messages → Send Media by Media ID](https://dualhook.com/docs/media-messages#send-media-by-media-id) . Delivery Sequence of Multiple Messages[](https://dualhook.com/docs/service-messages#delivery-sequence-of-multiple-messages) ---------------------------------------------------------------------------------------------------------------------------- When sending a series of messages, **delivery order is not guaranteed to match request order**. If sequence matters (e.g. an intro message before a follow-up question), wait for the `delivered` status webhook on message N before sending message N+1. For high-volume bursts where strict ordering doesn't matter (e.g. a list of independent updates), pipeline freely. Time-To-Live (TTL) and Dropped Messages[](https://dualhook.com/docs/service-messages#time-to-live-ttl-and-dropped-messages) ---------------------------------------------------------------------------------------------------------------------------- If Cloud API can't deliver a message immediately (recipient offline, network issue), it retries for a default TTL window: | Message kind | Default TTL | | --- | --- | | All service messages | 30 days | | Authentication templates | 10 minutes | | All other templates | 30 days | You can customize TTL per template (utility, authentication, and marketing-via-MM-API only). See Meta's Time-to-live documentation for the full TTL parameter reference. If you don't receive a `delivered` status webhook before the TTL elapses, **assume the message was dropped**. Failed sends emit a `failed` status webhook, but with a small possible delay — build a buffer before treating absence as failure. Message Quality[](https://dualhook.com/docs/service-messages#message-quality) ------------------------------------------------------------------------------ Quality is computed from the last seven days of recipient feedback signals, weighted by recency: blocks, reports, mutes, archives, and the reasons users provide when blocking you. To keep quality high: * Follow the WhatsApp Business Messaging Policy. * Only message users who have explicitly opted in. * Make messages personalized and useful — avoid generic greetings. * Don't send too many messages per day. * Optimize content and length for the audience. Per-number status, quality rating, and messaging limits are visible in WhatsApp Manager → Account tools → Phone numbers, and are also surfaced via [Health Monitoring](https://dualhook.com/docs/health-monitoring) in Dualhook. High-traffic numbers can shift quality state within minutes — see [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) for monitoring guidance. Pricing[](https://dualhook.com/docs/service-messages#pricing) -------------------------------------------------------------- Through **September 30, 2026**, service messages sent inside an open CSW are free. From **October 1, 2026**, each phone number gets 1,000 free service messages per month; only the 1,001st onward is charged. The allowance refreshes monthly, does not roll over, and is separate for each phone number. Paid service messages use the utility and authentication list rate for the recipient's market and do not receive volume-tier discounts. Service messages inside a 72-hour free entry point window remain free. The status-webhook `pricing.category` remains `service`; read `billable` and `type` to determine how Meta treated a delivery. Meta has documented `type: regular` for a billable service message but has not yet documented the exact `type` for a delivery covered by the new monthly free tier. See [Pricing → Free vs Billed Messages](https://dualhook.com/docs/pricing#free-vs-billed-messages) and [WhatsApp Pricing Changes 2026](https://dualhook.com/docs/whatsapp-pricing-changes-2026) . Related ------- * [Interactive Service Messages](https://dualhook.com/docs/service-messages-interactive) Interactive WhatsApp messages: CTA URL button, reply buttons, list, media carousel, and location request. Includes payload shapes and inbound-webhook formats for each type. * [Address Messages (India)](https://dualhook.com/docs/service-messages-address) India-only interactive form for collecting structured shipping addresses inside WhatsApp. Covers prefill, saved addresses, validation errors, the nfm\_reply submission webhook, and feature-not-supported (error 1026) fallback. * [Reaction Messages](https://dualhook.com/docs/service-messages-reactions) Apply emoji reactions to WhatsApp user messages. Limitations (sent-only status, 30-day window, error 131009), how to remove a reaction, and inbound user reaction webhooks. * [Media Messages](https://dualhook.com/docs/media-messages) Sending and receiving images, video, audio, documents, and stickers via Cloud API. * [Contacts & Location Messages](https://dualhook.com/docs/contacts-location) Sending and receiving contact cards and location messages via Cloud API. * [Messaging Overview](https://dualhook.com/docs/messaging-overview) Core messaging endpoint, text messages, formatting, delivery statuses, and common errors. * [Pricing](https://dualhook.com/docs/pricing) Dualhook subscription plans, what is included, and how pricing relates to Meta messaging fees. * [WhatsApp Pricing Changes 2026](https://dualhook.com/docs/whatsapp-pricing-changes-2026) Meta's 2026 WhatsApp pricing changes: Meta Business Agent billing, 1,000 free monthly service messages per number, chargeable service and in-window utility messages, and October rate cards. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Address Messages (India) | Dualhook Browse Docs Address Messages (India) ======================== India-only interactive form for collecting structured shipping addresses inside WhatsApp. Covers prefill, saved addresses, validation errors, the nfm\_reply submission webhook, and feature-not-supported (error 1026) fallback. What Address Messages Do[](https://dualhook.com/docs/service-messages-address#what-address-messages-do) -------------------------------------------------------------------------------------------------------- Address messages are an interactive [service message](https://dualhook.com/docs/service-messages) type that asks a WhatsApp user to fill in a structured shipping address inside WhatsApp itself. The user sees a form, completes it, and submits — your backend receives the structured address back via webhook. This eliminates the back-and-forth of asking for street, pin code, city, etc. as separate text questions and avoids typos in free-form text. Availability (India Only)[](https://dualhook.com/docs/service-messages-address#availability-india-only) -------------------------------------------------------------------------------------------------------- This feature is **only available for businesses based in India sending to Indian customers**. The form only shows on WhatsApp clients that support `address_message`. Older or non-supporting clients receive nothing visible — see [Feature Not Supported](https://dualhook.com/docs/service-messages-address#feature-not-supported-old-clients) for the webhook you get instead. `country` is a mandatory field in the action parameters; omitting it returns a validation error. Currently only `IN` is supported. Supported Fields[](https://dualhook.com/docs/service-messages-address#supported-fields) ---------------------------------------------------------------------------------------- | Field | Display label | Input type | Validation | | --- | --- | --- | --- | | `name` | Name | text | — | | `phone_number` | Phone Number | tel | Valid phone numbers only | | `in_pin_code` | Pin Code | text | Max length: 6 | | `house_number` | Flat/House Number | text | — | | `floor_number` | Floor Number | text | — | | `tower_number` | Tower Number | text | — | | `building_name` | Building/Apartment Name | text | — | | `address` | Address | text | — | | `landmark_area` | Landmark/Area | text | — | | `city` | City | text | — | | `state` | State | text | — | Send an Address Message[](https://dualhook.com/docs/service-messages-address#send-an-address-message) ------------------------------------------------------------------------------------------------------ Minimal payload — just the country and a body prompt: { "messaging_product": "whatsapp", "recipient_type": "individual", "to": "+91xxxxxxxxxx", "type": "interactive", "interactive": { "type": "address_message", "body": { "text": "Thanks for your order! Tell us what address you'd like this delivered to." }, "action": { "name": "address_message", "parameters": { "country": "IN" } } } } The user sees an empty form, fills it in, submits — and you receive the address via [webhook](https://dualhook.com/docs/service-messages-address#address-submission-webhook) . Prefill and Saved Addresses[](https://dualhook.com/docs/service-messages-address#prefill-and-saved-addresses) -------------------------------------------------------------------------------------------------------------- Two optional ways to reduce the user's friction: **Prefill specific fields** with `values`: "action": { "name": "address_message", "parameters": { "country": "IN", "values": { "name": "Pablo Morales", "phone_number": "+91xxxxxxxxxx" } } } The user can edit prefilled values before submitting. **Pass saved addresses** the user can pick from: "action": { "name": "address_message", "parameters": { "country": "IN", "saved_addresses": [\ {\ "id": "address1",\ "value": {\ "name": "Pablo Morales",\ "phone_number": "+91xxxxxxxxxx",\ "in_pin_code": "400063",\ "floor_number": "8",\ "building_name": "",\ "address": "Wing A, Cello Triumph, IB Patel Rd",\ "landmark_area": "Goregaon",\ "city": "Mumbai"\ }\ }\ ] } } The user sees the saved addresses with an option to pick one or enter a new address. Each saved address needs a unique `id` you assign — when the user picks one, the webhook returns that `id` so you can map it back to your CRM record. Validation Errors[](https://dualhook.com/docs/service-messages-address#validation-errors) ------------------------------------------------------------------------------------------ When you receive a submitted address that fails server-side validation, **re-send** the address message with the same `values` and a `validation_errors` map. WhatsApp blocks submission until the user fixes the flagged fields. "action": { "name": "address_message", "parameters": { "country": "IN", "values": { "name": "Pablo Morales", "phone_number": "+91xxxxxxxxxx", "in_pin_code": "666666", "address": "Some other location", "city": "Delhi" }, "validation_errors": { "in_pin_code": "We could not locate this pin code." } } } `validation_errors` is a flat map of `field_name → error_message`. The error text is shown to the user beneath the offending field. Address Submission Webhook[](https://dualhook.com/docs/service-messages-address#address-submission-webhook) ------------------------------------------------------------------------------------------------------------ When the user submits, you receive a Native Flow Message (NFM) reply on the standard `messages` webhook field: { "messages": [\ {\ "context": {\ "from": "15550001234",\ "id": "wamid.HBgLMTIwNjU1NTAxMDcVAgARGBI3NjNFN0U5QzMzNDlCQjY0M0QA"\ },\ "from": "+91xxxxxxxxxx",\ "id": "wamid.HBgLMTIwNjU1NTAxMDcVAgASGCA5RDhBNENEMEQ3RENEOEEzMEI0RUExRDczN0I1NThFQwA=",\ "timestamp": "1671498855",\ "type": "interactive",\ "interactive": {\ "type": "nfm_reply",\ "nfm_reply": {\ "name": "address_message",\ "response_json": "{\"saved_address_id\":\"address1\",\"values\":{\"in_pin_code\":\"400063\",\"building_name\":\"\",\"landmark_area\":\"Goregaon\",\"address\":\"Wing A, Cello Triumph, IB Patel Rd\",\"city\":\"Mumbai\",\"name\":\"Pablo Morales\",\"phone_number\":\"+91xxxxxxxxxx\",\"floor_number\":\"8\"}}",\ "body": "Pablo Morales\n +91xxxxxxxxxx\n 400063, Goregaon, Wing A, Cello Triumph, IB Patel Rd, Mumbai, 8"\ }\ }\ }\ ] } | Field | Notes | | --- | --- | | `interactive.type` | Always `nfm_reply` | | `nfm_reply.name` | Always `address_message` for this flow | | `nfm_reply.response_json` | **String-encoded JSON** containing `saved_address_id` (if user picked a saved one) and `values` (the submitted form fields) | | `nfm_reply.body` | Human-readable summary as the user sees it (optional) | `response_json` is a string, not a nested object — parse it with `JSON.parse` before consuming. In Dualhook setups, this webhook arrives on the `messages` field and is delivered directly to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) . Feature Not Supported (Old Clients)[](https://dualhook.com/docs/service-messages-address#feature-not-supported-old-clients) ---------------------------------------------------------------------------------------------------------------------------- If the recipient's WhatsApp client doesn't support `address_message` (older versions, certain platforms), the message is silently dropped and you receive a `failed` status webhook with error code **1026** (Receiver Incapable): { "statuses": [\ {\ "id": "wamid.HBgL...",\ "status": "failed",\ "timestamp": "1670394125",\ "type": "message",\ "recipient_id": "91xxxxxxxxxx",\ "errors": [\ { "code": 1026, "title": "Receiver Incapable" }\ ]\ }\ ] } Treat 1026 as "fall back to a text-based address question" — the user did not refuse, they just can't see the form. Typical Flow[](https://dualhook.com/docs/service-messages-address#typical-flow) -------------------------------------------------------------------------------- 1. Business sends an `address_message` (with `parameters.country: "IN"`, optionally `values` or `saved_addresses`). 2. User taps the CTA, fills the form, submits. 3. Webhook arrives with `type: "interactive"`, `interactive.type: "nfm_reply"`, `nfm_reply.name: "address_message"`, `nfm_reply.response_json` containing the address. 4. Backend validates server-side (e.g. real pin-code lookup, courier coverage check). 5. If validation fails, re-send the message with `values` (carrying over the user's input) plus `validation_errors`. 6. Loop until validation passes or user abandons. If the recipient's client doesn't support the form, the send fails with error 1026 and you should fall back to text-based collection. Related ------- * [Service Messages](https://dualhook.com/docs/service-messages) Free-form messages inside an open customer service window: types, replies, TTL, and October 2026 pricing with 1,000 free messages per number each month. * [Interactive Service Messages](https://dualhook.com/docs/service-messages-interactive) Interactive WhatsApp messages: CTA URL button, reply buttons, list, media carousel, and location request. Includes payload shapes and inbound-webhook formats for each type. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Capacity, Quality & Limits | Dualhook Browse Docs Capacity, Quality & Limits ========================== API rate limits, messaging limit tiers, quality rating, and scaling guidance. Throughput[](https://dualhook.com/docs/capacity-quality-limits#throughput) --------------------------------------------------------------------------- Each business [phone number](https://dualhook.com/docs/phone-numbers) can send up to **80 messages per second** by default (combined inbound and outbound across text, media, and template messages). Higher capacity is available through Meta on request. Throughput is per phone number, not per portfolio — adding more numbers under the same WABA is one way to scale total send rate. API Rate Limits[](https://dualhook.com/docs/capacity-quality-limits#api-rate-limits) ------------------------------------------------------------------------------------- Two layers of rate limiting apply at the Graph API level: ### App-level call limits[](https://dualhook.com/docs/capacity-quality-limits#app-level-call-limits) Calls your app makes against a WABA count toward an app-level request budget over a rolling one-hour window: | WABA state | Default limit (per app, per WABA) | | --- | --- | | New WABA, no registered phone number | 200 requests / hour | | Active WABA with at least one registered phone number | 5,000 requests / hour | These limits apply to management endpoints such as `/`, `//phone_numbers`, `//message_templates`, `//subscribed_apps`, and `//assigned_users`. ### Runtime symptoms[](https://dualhook.com/docs/capacity-quality-limits#runtime-symptoms) Rate limiting can also occur even when your account is healthy. Two common runtime modes: * **Request-rate limit** — returns `429` when request-per-second thresholds are exceeded. * **Concurrent-load limit** — returns `503` when upstream systems are temporarily overloaded. If either appears: 1. Slow down request pace immediately. 2. Use queue-based traffic shaping. 3. Retry with exponential backoff and jitter. For error code details, see [Meta API Error Reference](https://dualhook.com/docs/api-errors) . Pair Rate Limit (Per Recipient)[](https://dualhook.com/docs/capacity-quality-limits#pair-rate-limit-per-recipient) ------------------------------------------------------------------------------------------------------------------- Independent of throughput, a single business phone number can only send **1 message every 6 seconds to the same WhatsApp user** (≈10 messages/minute, 600/hour per pair). Exceeding this returns error `131056` until the pair is back within the allowed rate. Bursting is allowed: up to **45 messages in a 6-second burst** to the same user, but each burst "borrows" from future quota — after a burst of 20, you must wait roughly 2 minutes before sending more to that recipient. Recommended retry on `131056`: back off `4^X` seconds (start with `X=0`, increment on each repeated failure) until the send succeeds. The full symptom, cause, and fix guide is on [WhatsApp Error 131056](https://dualhook.com/docs/whatsapp-error-131056) . Messaging Limits (Portfolio-Level)[](https://dualhook.com/docs/capacity-quality-limits#messaging-limits-portfolio-level) ------------------------------------------------------------------------------------------------------------------------- Messaging limits define how many unique users your business can message outside the customer service window in a rolling 24-hour period. Limits are shared at the [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) level — all numbers in the same portfolio consume the same messaging pool. Typical tiers: | Tier | Unique users / 24h | | --- | --- | | `TIER_250` | 250 | | `TIER_2K` | 2,000 | | `TIER_10K` | 10,000 | | `TIER_100K` | 100,000 | | `TIER_UNLIMITED` | No cap | **Important:** User-initiated conversations are not counted toward this limit. Limits only apply to business-initiated outreach outside the service window. Limit Fields[](https://dualhook.com/docs/capacity-quality-limits#limit-fields) ------------------------------------------------------------------------------- Meta shifted to portfolio-level limit handling on **October 7, 2025**. Request the current portfolio-level field when checking status from the phone-number node. | Field | Status | | --- | --- | | `whatsapp_business_manager_messaging_limit` | Current (portfolio-level) | | `current_limit` | Used in some webhook events | Dualhook uses `whatsapp_business_manager_messaging_limit` for new health checks. Some management webhook events still include `current_limit`, so keep handling that event payload field separately. Increasing Limits[](https://dualhook.com/docs/capacity-quality-limits#increasing-limits) ----------------------------------------------------------------------------------------- Limit growth happens in stages: 1. Scale from `250` to `2,000` 2. Scale from `2,000` upward (`10K`, `100K`, unlimited) Common qualification factors: * [Business verification](https://dualhook.com/docs/meta-business-verification) path completion * Consistent high-quality messaging * Sufficient recent usage of current limit After eligibility is met, upgrades are often applied automatically within a few hours. ### Automatic Scaling Beyond 2,000[](https://dualhook.com/docs/capacity-quality-limits#automatic-scaling-beyond-2000) Meta evaluates: * Message quality across numbers and [templates](https://dualhook.com/docs/templates) * Utilization of current limit over the last 7 days If criteria are satisfied, the portfolio moves up one tier automatically. Quality Rating[](https://dualhook.com/docs/capacity-quality-limits#quality-rating) ----------------------------------------------------------------------------------- Quality is based on recent user feedback signals, weighted by recency: * Blocks * Reports * Read/engagement behavior * Negative feedback patterns When quality degrades: * Upgrade eligibility can be delayed * Sending restrictions can appear * Health may move from `AVAILABLE` to `LIMITED` or `BLOCKED` (see [Health Monitoring](https://dualhook.com/docs/health-monitoring) ) Template Quality States[](https://dualhook.com/docs/capacity-quality-limits#template-quality-states) ----------------------------------------------------------------------------------------------------- [Template](https://dualhook.com/docs/templates) \-level quality is tracked separately from number-level health. | State | Meaning | | --- | --- | | `ACTIVE - QUALITY_PENDING` | Not enough data to rate | | `ACTIVE - HIGH QUALITY` | Strong user satisfaction | | `ACTIVE - MEDIUM QUALITY` | Early dissatisfaction signals | | `ACTIVE - LOW QUALITY` | High dissatisfaction risk | Low template quality can lead to pauses or disablement even if account-level access is still active. Check Current Status[](https://dualhook.com/docs/capacity-quality-limits#check-current-status) ----------------------------------------------------------------------------------------------- React to `phone_number_quality_update`, `account_update`, `account_review_update`, and `business_capability_update` webhooks first. Use the read below only for reconciliation, at most once every five minutes. The phone health response includes the related WABA, business, and app summary, so do not poll the WABA separately for the same health check. curl -X GET "https://api.dualhook.com/v25.0/?fields=health_status,quality_rating,whatsapp_business_manager_messaging_limit" \ -H "Authorization: Bearer " { "id": "", "health_status": { "can_send_message": "AVAILABLE", "entities": [] }, "quality_rating": "GREEN", "whatsapp_business_manager_messaging_limit": "TIER_10K" } Webhook Signals to Monitor[](https://dualhook.com/docs/capacity-quality-limits#webhook-signals-to-monitor) ----------------------------------------------------------------------------------------------------------- Dualhook surfaces management events for operations monitoring. See [Webhook Events](https://dualhook.com/docs/webhook-events) for the complete event list. ### phone\_number\_quality\_update[](https://dualhook.com/docs/capacity-quality-limits#phone_number_quality_update) { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "changes": [\ {\ "field": "phone_number_quality_update",\ "value": {\ "event": "QUALITY_UPDATED",\ "phone_number_id": "",\ "display_phone_number": "",\ "current_limit": "TIER_2K",\ "current_quality_rating": "YELLOW",\ "previous_quality_rating": "GREEN"\ }\ }\ ]\ }\ ] } ### business\_capability\_update[](https://dualhook.com/docs/capacity-quality-limits#business_capability_update) { "object": "whatsapp_business_account", "entry": [\ {\ "id": "",\ "changes": [\ {\ "field": "business_capability_update",\ "value": {\ "event": "increased_capabilities_eligibility_deferred"\ }\ }\ ]\ }\ ] } Monitoring Locations[](https://dualhook.com/docs/capacity-quality-limits#monitoring-locations) ----------------------------------------------------------------------------------------------- Use both product and Meta surfaces: * **Dualhook dashboard** — webhook-driven health and limit/quality signals * **WhatsApp Manager** — Messaging Limits and account insights * **Meta Account Quality** — `business.facebook.com/accountquality` Operational Playbook[](https://dualhook.com/docs/capacity-quality-limits#operational-playbook) ----------------------------------------------------------------------------------------------- If quality drops or limit upgrades stall: 1. Reduce promotional volume and tighten audience targeting. 2. Send only to recently opted-in users. 3. Improve [template](https://dualhook.com/docs/templates) relevance and frequency control. 4. Monitor blocks/reports daily and pause weak campaigns quickly. 5. Resume scale gradually only after sustained quality recovery. For a comprehensive health maintenance strategy, see [Maintain Account Health](https://dualhook.com/docs/account-health) . Related ------- * [WhatsApp Error 131056: Pair Rate Limit Hit](https://dualhook.com/docs/whatsapp-error-131056) WhatsApp error 131056 means too many messages to the same recipient too fast (pair rate limit). How to pace per-recipient sends, back off, and prevent it. * [Messaging Health Status](https://dualhook.com/docs/health-monitoring) Monitor phone number health: AVAILABLE, LIMITED, BLOCKED states and quality rating. * [Maintain Account Health](https://dualhook.com/docs/account-health) Ownership hygiene, quality monitoring, opt-out governance, and incident playbook. * [Marketing Messages](https://dualhook.com/docs/marketing-messages) Marketing template messaging through Dualhook's standard Cloud API runtime, plus boundaries for Meta's separate MM API product. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Dualhook + Zapier: Gateway Pattern for WhatsApp Webhooks and Template Sends | Dualhook Browse Docs Zapier Webhook Integration ========================== Use a lightweight gateway for Meta verification, then forward approved WhatsApp events into Zapier for downstream automation. Prerequisites[](https://dualhook.com/docs/guide-zapier-webhook#prerequisites) ------------------------------------------------------------------------------ * An active [Dualhook connection](https://dualhook.com/docs/getting-started) * A lightweight HTTPS gateway endpoint that you control * Your Meta webhook verify token (for the GET handshake — set during connection setup in Dualhook) * Your `WABA_ID` and `PHONE_NUMBER_ID` for payload-side validation in the gateway * A Zapier account with either: * **Webhooks by Zapier** for receiving forwarded events, or * **API by Zapier** / **Webhooks by Zapier** for outbound Graph API requests If you need a starting point for the gateway itself, use one of the direct receiver guides: * [Node.js Webhook Receiver](https://dualhook.com/docs/guide-nodejs-webhook) * [Laravel Webhook Integration](https://dualhook.com/docs/guide-laravel-webhook) Why Zapier Is Not the Direct Meta Webhook Receiver[](https://dualhook.com/docs/guide-zapier-webhook#why-zapier-is-not-the-direct-meta-webhook-receiver) -------------------------------------------------------------------------------------------------------------------------------------------------------- Dualhook's direct-routing model expects your Meta callback URL to complete Meta's verification handshake: * Meta sends `GET` with `hub.mode`, `hub.verify_token`, and `hub.challenge` * your endpoint must return the raw `hub.challenge` body with `200` Zapier's Catch Hook and Catch Raw Hook triggers can receive webhook traffic, but they do not provide the response control needed to reliably act as the Meta verification endpoint. That makes Zapier a poor fit for the first hop in a Dualhook webhook flow. Zapier is still useful after the first hop. The safe pattern is: 1. a small gateway endpoint receives Meta webhooks 2. the gateway validates the payload and responds to Meta 3. the gateway forwards approved events into Zapier > **No `META_APP_SECRET`?** In the Embedded Signup flow `X-Hub-Signature-256` is signed by Dualhook's Meta app and isn't customer-verifiable — the gateway validates inbound webhook payload shape instead. See [Messaging Webhook → Inbound Webhook POST Validation](https://dualhook.com/docs/messaging-webhook#signature-validation-post) > . Architecture Overview[](https://dualhook.com/docs/guide-zapier-webhook#architecture-overview) ---------------------------------------------------------------------------------------------- Meta (messages, statuses, errors) -> Your gateway endpoint -> GET verification (verify_token) -> inbound payload validation (envelope, WABA ID, phone_number_id) -> fast 200 to Meta -> forward normalized event to Zapier Zapier -> downstream automation -> optional sends through api.dualhook.com Dualhook -> setup, monitoring, template operations, management events This keeps Dualhook out of message storage while still acknowledging that your gateway and Zapier can both become storage layers if they keep request bodies, task history, or logs. Build the Gateway Endpoint[](https://dualhook.com/docs/guide-zapier-webhook#build-the-gateway-endpoint) -------------------------------------------------------------------------------------------------------- The gateway can be a small Node.js service, Laravel controller, edge function, or similar HTTPS endpoint. It must do four things reliably. ### 1\. Handle GET verification[](https://dualhook.com/docs/guide-zapier-webhook#1-handle-get-verification) For `GET` verification requests: * verify that `hub.mode` is `subscribe` * compare `hub.verify_token` to your expected token * return the raw `hub.challenge` value with `200` * return `403` on mismatch ### 2\. Validate inbound POST payloads[](https://dualhook.com/docs/guide-zapier-webhook#2-validate-inbound-post-payloads) For `POST` webhook requests: * confirm `object === "whatsapp_business_account"` * confirm `entry[].id` equals your stored `WABA_ID` * confirm `entry[].changes[].field` is `messages` * confirm `value.metadata.phone_number_id` is present and equals your stored `PHONE_NUMBER_ID` * return `401` and stop if any check fails ### 3\. Respond quickly[](https://dualhook.com/docs/guide-zapier-webhook#3-respond-quickly) Return `200` to Meta immediately after inbound payload validation passes. Do heavy work asynchronously. ### 4\. Forward a normalized event into Zapier[](https://dualhook.com/docs/guide-zapier-webhook#4-forward-a-normalized-event-into-zapier) Forward only what Zapier needs. A practical forwarded shape includes: { "trace_id": "internal_trace_id", "received_at": "2026-04-08T12:00:00Z", "event_type": "messages", "phone_number_id": "123456789012345", "payload": { "object": "whatsapp_business_account", "entry": [] } } If you want the gateway reference implementation, adapt the verification and payload-validation logic from [Node.js Webhook Receiver](https://dualhook.com/docs/guide-nodejs-webhook) or [Laravel Webhook Integration](https://dualhook.com/docs/guide-laravel-webhook) . Trigger Zapier from the Gateway[](https://dualhook.com/docs/guide-zapier-webhook#trigger-zapier-from-the-gateway) ------------------------------------------------------------------------------------------------------------------ If the gateway is forwarding normalized JSON, **Catch Hook** is usually the simplest Zap trigger. Use **Catch Raw Hook** only when you deliberately want the forwarded body unparsed and are sure it stays within Zapier's size limits. Recommended pattern: 1. Build a Zap with **Webhooks by Zapier** as the trigger. 2. Copy the Zapier hook URL. 3. Have the gateway `POST` the normalized event to that URL after Meta has already received `200`. 4. Continue the rest of your automation in Zapier. This split keeps the Meta-facing contract under your control and keeps Zapier focused on downstream automation, notifications, enrichment, or app-to-app steps. Send Template Messages via Dualhook[](https://dualhook.com/docs/guide-zapier-webhook#send-template-messages-via-dualhook) -------------------------------------------------------------------------------------------------------------------------- For outbound sends from Zapier, prefer **API by Zapier** when you want credentials stored in an app connection rather than inside a Zap step. Use **Webhooks by Zapier** only when simple auth or no auth is enough and you accept step-level credential visibility. ### Preferred: API by Zapier[](https://dualhook.com/docs/guide-zapier-webhook#preferred-api-by-zapier) Use **API by Zapier -> API Request** with: * Method: `POST` * URL: `https://api.dualhook.com/v25.0//messages` * Authentication: static headers or OAuth if your setup requires it * Domain filter: restrict to `api.dualhook.com` ### Simpler fallback: Webhooks by Zapier[](https://dualhook.com/docs/guide-zapier-webhook#simpler-fallback-webhooks-by-zapier) If you are comfortable with step-level auth handling, **Webhooks by Zapier** can also send the request: * Method: `POST` * Headers: * `Authorization: Bearer ` * `Content-Type: application/json` Example template send: { "messaging_product": "whatsapp", "to": "12015550123", "type": "template", "template": { "name": "order_update_v1", "language": { "code": "en_US" } } } See [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) for richer payload patterns. Test with Dualhook[](https://dualhook.com/docs/guide-zapier-webhook#test-with-dualhook) ---------------------------------------------------------------------------------------- 1. Put the gateway URL, not the Zapier URL, into Dualhook as the webhook URL. 2. Use the same verify token in Dualhook and in the gateway. 3. Confirm the gateway completes Meta verification successfully. 4. Run Dualhook **Test Ping** to confirm the endpoint is reachable. 5. Send a real WhatsApp message to your number. 6. Confirm that: * the gateway accepts real Meta payloads and rejects unrelated test pings * Meta receives a fast `200` * the gateway forwards the event to Zapier * the Zap runs on the forwarded event Production Caveats[](https://dualhook.com/docs/guide-zapier-webhook#production-caveats) ---------------------------------------------------------------------------------------- * Do not use Zapier as the primary durability layer for Meta webhooks. The gateway should own retries, idempotency, and backoff when Zapier is slow or rate-limited. * Zapier can delay processing under high load, and webhook/task histories can retain payload data. Treat that as part of your compliance boundary. * Catch Raw Hook payloads are capped at 2 MB. Keep forwarded events small and predictable. * Use idempotency in the gateway before forwarding and in Zapier before acting on repeated events. * Treat message payloads as evolving. The BSUID rollout makes some identifiers conditional, so your gateway normalization should not assume phone-number-based fields are always present. See [BSUID Transition Guide](https://dualhook.com/docs/contacts-bsuid) . Related ------- * [Node.js Webhook Receiver](https://dualhook.com/docs/guide-nodejs-webhook) Set up a Node.js Express endpoint to receive WhatsApp webhooks routed by Dualhook. * [Laravel Webhook Integration](https://dualhook.com/docs/guide-laravel-webhook) Build a Laravel webhook controller for WhatsApp messages routed through Dualhook. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) How to send template messages via Cloud API with variables, media headers, and URL buttons. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Messaging Best Practices: Opt-In, Quality, Webhooks, Launch Checklist | Dualhook Browse Docs Messaging Best Practices ======================== Opt-in, notification quality, contact validation, callback handling, and pre-launch checklist. The practices that keep a WhatsApp number's quality rating green and its messaging limits growing: explicit opt-in per category, notification quality rules, E.164 contact validation, idempotent sends, and a webhook receiver that acknowledges fast. Because Dualhook routes message-path webhooks straight to your server, most of this applies to code you own, and the pre-launch checklist at the end is the one Dualhook support asks about first. Scope in Dualhook[](https://dualhook.com/docs/messaging-best-practices#scope-in-dualhook) ------------------------------------------------------------------------------------------ Because Dualhook uses [Webhook Override](https://dualhook.com/docs/webhook-override) , message webhooks (`messages`, `statuses`, `errors`) go directly from Meta to your endpoint. These best practices primarily apply to your sending service and your webhook receiver. Opt-In and Consent[](https://dualhook.com/docs/messaging-best-practices#opt-in-and-consent) -------------------------------------------------------------------------------------------- Active user consent is required before business-initiated messaging. * Collect opt-in through explicit user action. * Separate opt-ins by message category when possible (`marketing`, `utility`, `authentication`). * Make opt-out instructions clear and easy to use. * Store consent evidence (source, timestamp, channel, category) for auditability. **Policy reminder:** Business-initiated outreach must use approved [templates](https://dualhook.com/docs/templates) . Sending without consent increases blocks and reports, which reduces [quality and sending limits](https://dualhook.com/docs/capacity-quality-limits) . Notification Quality Rules[](https://dualhook.com/docs/messaging-best-practices#notification-quality-rules) ------------------------------------------------------------------------------------------------------------ For notification traffic, messages should be: * **Expected** — the recipient understands why they received it. * **Relevant** — content is concise, useful, and contextual. * **Timely** — sent when it is most useful. Poor quality signals (blocks/reports) can lead to reduced messaging limits, temporary restrictions, and account suspension risk. Contact Validation[](https://dualhook.com/docs/messaging-best-practices#contact-validation) -------------------------------------------------------------------------------------------- Validate phone numbers in E.164 format before sending. Important behavior: * `POST //contacts` can validate whether a phone number is registered on WhatsApp and return `wa_id`, but it does not return the new BSUID `user_id` values. * `wa_id` is returned on successful phone-number sends. With Meta's BSUID rollout and later username launch, `wa_id` may be omitted from webhooks if the user has adopted a WhatsApp username and none of the [phone-number inclusion conditions](https://dualhook.com/docs/contacts-bsuid#when-phone-numbers-still-appear) are met. The new `user_id` (BSUID) field is the stable identifier going forward. * Handle `131026` (`Message Undeliverable`) as a likely invalid or unreachable recipient case. See the [BSUID transition guide](https://dualhook.com/docs/contacts-bsuid) for the full timeline, and the [BSUID webhook changes reference](https://dualhook.com/docs/contacts-bsuid-webhooks) for field-level migration guidance. ### Successful Send Response[](https://dualhook.com/docs/messaging-best-practices#successful-send-response) { "messaging_product": "whatsapp", "contacts": [\ {\ "input": "12015550123",\ "wa_id": "12015550123"\ }\ ], "messages": [\ {\ "id": "wamid.HBgLMTIwMTU1NTAxMjMVAgARGBI..."\ }\ ] } Message Sending Reliability[](https://dualhook.com/docs/messaging-best-practices#message-sending-reliability) -------------------------------------------------------------------------------------------------------------- General rules: * Use `POST //messages` for all message types. * Stop free-form replies if you receive `470` (expired customer service window) and switch to templates. * Keep text message bodies at `<= 4096` characters. * If `preview_url: true`, ensure a valid URL is present in the message body. Media rules: * Respect supported MIME types, codecs, and file-size limits (see [Media Messages](https://dualhook.com/docs/media-messages) ). * For repeated sends, upload once and reuse `media_id` instead of repeatedly passing hosted URLs (see [Upload, Retrieve & Delete Media](https://dualhook.com/docs/media-upload) ). ### Deep Links[](https://dualhook.com/docs/messaging-best-practices#deep-links) Prefer Universal Links over app deep links for broader cross-device reliability: https://wa.me/?text= Callback Handling[](https://dualhook.com/docs/messaging-best-practices#callback-handling) ------------------------------------------------------------------------------------------ Design your webhook receiver as: **acknowledge first, process later.** * Return `HTTP 200` immediately after authenticity and basic validation. * Queue and process business logic asynchronously. * Deduplicate webhook events using `wamid` + status fields. * Keep certificate chain valid and DNS publicly resolvable for your callback host. See [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) for payload structure and implementation details. Latency and Delivery Expectations[](https://dualhook.com/docs/messaging-best-practices#latency-and-delivery-expectations) -------------------------------------------------------------------------------------------------------------------------- Not all delays are API-side. Delivery delays can be caused by recipient device or network conditions (low connectivity, app backgrounding, OS notification delays). When troubleshooting: 1. Verify webhook ack latency and error rate first. 2. Confirm message status webhooks are arriving (`sent`, `delivered`, `read`). 3. Confirm recipient device, app, and network state if delivery appears delayed. Chatbot Integrations[](https://dualhook.com/docs/messaging-best-practices#chatbot-integrations) ------------------------------------------------------------------------------------------------ During rollout, set a clear fallback message when bot capability is incomplete: Thanks for contacting us. This automation is currently in rollout and may not answer all requests yet. For urgent help, reply HUMAN. ### Chatbot Quality Checklist[](https://dualhook.com/docs/messaging-best-practices#chatbot-quality-checklist) Before launch, review: * **Branding** — tone and language match your brand. * **Performance** — bot intent is clear and answers common queries correctly. * **Consistency** — flows are reliable and regularly updated. * **Humanity** — bot identifies itself and exposes clear human handoff options. **Policy requirement:** If automation cannot resolve an issue, provide a human escalation path (live agent handoff, support phone, support email/web form, or equivalent). Pre-Launch Checklist[](https://dualhook.com/docs/messaging-best-practices#pre-launch-checklist) ------------------------------------------------------------------------------------------------ Before scaling sends: 1. Verify category-specific opt-ins are in place. 2. Confirm [template](https://dualhook.com/docs/templates) quality and status is healthy. 3. Warm up new numbers gradually. 4. Test with internal recipients first (including status webhook flow). 5. Validate retry and idempotency behavior under webhook failure simulation. For error handling guidance, see [Meta API Error Reference](https://dualhook.com/docs/api-errors) . For account health management, see [Maintain Account Health](https://dualhook.com/docs/account-health) . Related ------- * [Maintain Account Health](https://dualhook.com/docs/account-health) Ownership hygiene, quality monitoring, opt-out governance, and incident playbook. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. * [Messaging Overview](https://dualhook.com/docs/messaging-overview) Core messaging endpoint, text messages, formatting, delivery statuses, and common errors. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp API Architecture & Security | Dualhook Browse Docs WhatsApp API Architecture & Security ==================================== Cloud API architecture, encryption, data residency, GDPR, and Dualhook's direct-routing security model. Looking for the high-level overview first? Start with [WhatsApp API Privacy](https://dualhook.com/whatsapp-api-privacy) and then return here for the implementation details. Cloud API Architecture[](https://dualhook.com/docs/architecture-security#cloud-api-architecture) ------------------------------------------------------------------------------------------------- Since October 2025, WhatsApp Business Platform runs on Cloud API. At a high level: * Businesses send messages through Meta Graph API * Businesses receive events through webhooks * Meta Cloud API acts as the managed messaging intermediary between business systems and WhatsApp transport Encryption Model[](https://dualhook.com/docs/architecture-security#encryption-model) ------------------------------------------------------------------------------------- WhatsApp messaging uses the Signal protocol for encryption. Cloud API also applies standard platform security controls: * **Data in transit** — `HTTPS` + `TLS` * **Data at rest** — encrypted storage on Meta infrastructure Security boundary: * WhatsApp transport does not expose message content to WhatsApp operators. * Cloud API processes message content to provide platform functionality on behalf of the business. Message Flow[](https://dualhook.com/docs/architecture-security#message-flow) ----------------------------------------------------------------------------- ### User to Business[](https://dualhook.com/docs/architecture-security#user-to-business) 1. User sends a message. 2. Message is encrypted in transit to Cloud API. 3. Cloud API processes and decrypts for delivery to the business webhook. 4. Data is temporarily retained for core platform functionality (e.g. retries and retransmissions). ### Business to User[](https://dualhook.com/docs/architecture-security#business-to-user) 1. Business sends message request to Cloud API. 2. Cloud API temporarily stores and processes for delivery and reliability. 3. Cloud API encrypts for destination delivery. 4. WhatsApp transport delivers to the user client. In Dualhook setups, inbound messages arrive at your endpoint directly from Meta via [Webhook Override](https://dualhook.com/docs/webhook-override) . For outbound operations, your backend calls Dualhook's exact allowlisted Graph-compatible runtime routes with a per-connection `dh_live_...` key. The surface covers messages, media, selected templates, health/status, profiles, safe phone/calling settings, and QR codes without becoming an arbitrary Graph gateway. All native authenticated Graph calls made by Dualhook pass through a server-only transport that derives Meta's app-secret proof from the exact authorizing token. Neither the token nor the proof is exposed to customers. The runtime resolves the existing authoritative routing projection before decrypting authorization, compares every route to the connection's stored phone/WABA assets, verifies template and media object ownership, rejects customer-controlled fields/hosts/callbacks, and uses manual redirect handling so credentials cannot be forwarded to another origin. OAuth code exchange (before a token exists), inbound webhook signatures, and validated Facebook/CDN media downloads are separate documented flows rather than Graph proof exceptions inferred by callers. Local Storage (Data Residency)[](https://dualhook.com/docs/architecture-security#local-storage-data-residency) --------------------------------------------------------------------------------------------------------------- Cloud API supports local storage for message data at rest in selected regions. This is useful for businesses with regulatory or residency requirements. Data localized by Local Storage can include: * Incoming and outgoing messages * Text, media, and template payloads * Limited metadata needed for linkage and auditing ### Supported Regions (as of February 2026)[](https://dualhook.com/docs/architecture-security#supported-regions-as-of-february-2026) | Region | Countries | | --- | --- | | APAC | India, Singapore, Indonesia, South Korea, Japan, Australia | | LATAM | Brazil | | MEA | South Africa, Bahrain | | Europe | EU (Germany), UK, Switzerland | | North America | Canada | Region availability can change. Always validate against current Meta documentation before rollout. ### Enabling Local Storage[](https://dualhook.com/docs/architecture-security#enabling-local-storage) 1. Request Local Storage for a specific [WABA](https://dualhook.com/docs/whatsapp-business-accounts) through support. 2. Specify target storage region. 3. Confirm change with the PIN sent to the WABA [phone number](https://dualhook.com/docs/phone-numbers) . 4. Verify data location after activation. Rollback to default region typically requires a support request as well. Stored and Collected Data[](https://dualhook.com/docs/architecture-security#stored-and-collected-data) ------------------------------------------------------------------------------------------------------- Cloud API stores and processes operational data needed for platform behavior. Published guidance describes temporary message retention up to 30 days for core functionality (such as retries and retransmissions). Business identity and config data is managed in Meta business systems. Consumer identifiers used for delivery are processed for message routing. GDPR and Shared Responsibility[](https://dualhook.com/docs/architecture-security#gdpr-and-shared-responsibility) ----------------------------------------------------------------------------------------------------------------- Meta provides platform controls and documentation to support compliance. Each business remains responsible for its own legal compliance obligations (GDPR and other applicable laws). Platform security controls do not replace your internal privacy, access-control, and retention policies. Dualhook Security Model[](https://dualhook.com/docs/architecture-security#dualhook-security-model) --------------------------------------------------------------------------------------------------- Dualhook follows a stricter privacy boundary than what Cloud API requires: * Message content is routed directly from Meta to your webhook endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) . * Inbound message-path webhooks go directly from Meta to your endpoint and are not proxied by Dualhook. * Outbound message and media payloads transit Dualhook's runtime without body persistence, caching, or content logging. Successful allowlisted phone/WABA metadata reads use a connection-isolated 60-second cache; message, media, template, profile, error, and mutation responses are never cached. * Meta tokens, app secrets, generated proofs, Authorization headers, and credential-bearing Meta URLs are excluded from logs, persisted errors, analytics, and customer responses. * Structured, quoted-JSON, and JSON-encoded error fields pass through the same credential sanitizer. The runtime preserves safe public Meta error fields while sanitizing non-success JSON before relay. * Management responses use explicit projections; SIP credentials, encryption material, storage settings, callback configuration, and raw pagination URLs are removed even if Meta returns them unexpectedly. * Customer-controlled webhook subscriptions or phone-level webhook overrides are not exposed. WABA routing and offboarding remain owned by Dualhook's existing lifecycle services. * Dualhook stores only operational metadata needed for setup, monitoring, and [compliance](https://dualhook.com/docs/compliance) features. See [Compliance & Data Retention](https://dualhook.com/docs/compliance) for retention windows and export options. Operational Security Checklist[](https://dualhook.com/docs/architecture-security#operational-security-checklist) ----------------------------------------------------------------------------------------------------------------- * Protect each connection's `dh_live_...` key and rotate it from the connection Overview page. Dualhook does not expose its customer-scoped Meta authorization tokens. * Validate inbound webhook POSTs (envelope, WABA ID, `phone_number_id`), keep the override URL high-entropy and private, and enforce HTTPS. See [Messaging Webhook → Inbound Webhook POST Validation](https://dualhook.com/docs/messaging-webhook#signature-validation-post) . * Use least-privilege roles for [Meta portfolio](https://dualhook.com/docs/meta-business-portfolio) and system users. * Enable and manage [two-step verification](https://dualhook.com/docs/two-step-verification) on business numbers. * Monitor [quality](https://dualhook.com/docs/capacity-quality-limits) , account alerts, and security-related [webhook events](https://dualhook.com/docs/webhook-events) . * Keep clear audit trails for template changes, number ownership, and webhook endpoint changes. Related ------- * [Compliance & Data Retention](https://dualhook.com/docs/compliance) Privacy boundary, configurable retention, CSV export, and organization isolation. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Chatwoot and Dualhook: Safe WhatsApp API Upgrade Guide | Dualhook Browse Docs Chatwoot WhatsApp Integration ============================= Choose the safe Chatwoot migration path for customer-owned and Dualhook Meta apps across Chatwoot Cloud and self-hosted deployments. This guide covers the API security upgrade for Chatwoot integrations. The correct path depends on **which Meta app authorizes your outbound WhatsApp requests**. Identify that first; do not replace a working credential as a test. Choose Your Path[](https://dualhook.com/docs/guide-chatwoot-webhook#choose-your-path) -------------------------------------------------------------------------------------- | Current authorization | Chatwoot deployment | Required action | | --- | --- | --- | | Your company owns the Meta app and Meta access token used by Chatwoot | Cloud or self-hosted | Keep the native WhatsApp Cloud inbox pointed at `graph.facebook.com`. Do not replace the Meta token with a Dualhook key. | | Dualhook's Meta app authorizes outbound operations | Chatwoot Cloud | Do not put a `dh_live_...` key in the native WhatsApp token field. Use a customer-owned Meta app or a fully implemented API-channel bridge. | | Dualhook's Meta app authorizes outbound operations | Self-hosted Chatwoot | Deploy and test a per-inbox Dualhook provider adapter before changing the credential. Changing only `WHATSAPP_CLOUD_BASE_URL` is not sufficient. | If you are unsure which case applies, check who created and controls the permanent Meta token currently stored in the Chatwoot inbox. A token created from your own Meta app or system user remains a customer-owned Meta credential. A connection-scoped `dh_live_...` key is a Dualhook credential and is valid only at `api.dualhook.com`. Customer-Owned Meta App[](https://dualhook.com/docs/guide-chatwoot-webhook#customer-owned-meta-app) ---------------------------------------------------------------------------------------------------- This is the safest and lowest-maintenance Chatwoot configuration. If it is already working, **the API Upgrade page requires no Chatwoot change**. Continue to use: https://graph.facebook.com Authorization: Bearer Do not replace the token with `dh_live_...`, and do not redirect the inbox to Dualhook. The Dualhook runtime migration applies only to operations authorized through Dualhook's Meta app. For a new or migrated native inbox: 1. In your Meta business, confirm that your app and system user have access to the WABA and Phone Number ID. 2. Create a permanent system-user token with the WhatsApp permissions required by Chatwoot. 3. In Chatwoot, use the manual WhatsApp Cloud setup and enter the display phone number, Phone Number ID, WABA ID, and your Meta token. 4. Keep the native Meta Graph endpoint. Do not enter a Dualhook runtime key. 5. Configure the callback and verification token shown by Chatwoot, or preserve an existing working callback while you test the customer-owned app subscription. 6. Test inbound text and media, an in-window reply, a template send, an outbound attachment, delivered/read statuses, and template synchronization. 7. Remove an obsolete Dualhook Webhook Override only after the customer-owned app has passed all inbound and outbound tests. When converting an existing Chatwoot Embedded Signup inbox, prefer Chatwoot's [documented migration to manual setup](https://www.chatwoot.com/hc/user-guide/articles/1783938358-migrate-a-whats_app-embedded-signup-inbox-to-manual-setup) instead of deleting the inbox. Chatwoot states that its migration preserves the inbox and its conversations and settings. Dualhook Meta App[](https://dualhook.com/docs/guide-chatwoot-webhook#dualhook-meta-app) ---------------------------------------------------------------------------------------- Outbound requests for this case must use the connection's exact v25.0 runtime routes and a bearer-only credential: POST https://api.dualhook.com/v25.0//messages Authorization: Bearer dh_live_... The runtime is an allowlisted WhatsApp API, not a general Graph proxy. It does not accept Meta tokens, an `access_token` query parameter, `appsecret_proof`, app secrets, arbitrary Graph paths, or webhook-management calls. See [Runtime API Authentication](https://dualhook.com/docs/runtime-api) and the [Runtime API Endpoint Reference](https://dualhook.com/docs/runtime-api-reference) . ### Chatwoot Cloud[](https://dualhook.com/docs/guide-chatwoot-webhook#chatwoot-cloud) There is no safe native credential swap. Chatwoot Cloud does not expose an installation environment variable or custom native WhatsApp provider per inbox. Pasting `dh_live_...` into its Meta access-token field would cause at least some requests to go to Meta with the wrong credential. Use one of these options: 1. **Recommended:** authorize the number through your own Meta app and use Chatwoot's native manual WhatsApp Cloud inbox. 2. Use a customer-controlled bridge with a Chatwoot API inbox. The bridge must implement inbound/outbound mapping, media, templates, delivery statuses, retries, idempotency, and conversation continuity. Chatwoot documents the [API inbox and callback model](https://www.chatwoot.com/hc/user-guide/articles/1677839703-how-to-create-an-api-channel-inbox) . An API inbox is not a drop-in conversion of a native WhatsApp inbox. Plan how to retain historical conversations and test the bridge with a controlled number or conversation before changing production webhook routing. ### Self-Hosted Chatwoot[](https://dualhook.com/docs/guide-chatwoot-webhook#self-hosted-chatwoot) Use a **per-inbox Dualhook provider adapter**. Dualhook does not currently claim that unmodified Chatwoot is compatible with a `dh_live_...` credential. The adapter must: 1. Activate only for an explicitly marked Dualhook inbox. Do not redirect every WhatsApp Cloud inbox on the installation. 2. Build all runtime calls from `https://api.dualhook.com/v25.0`. 3. Send `dh_live_...` only as `Authorization: Bearer`; remove `access_token` and `appsecret_proof` from URLs, query strings, and bodies. 4. Route text, template, interactive, and attachment sends through the v25.0 `/messages` route. 5. Read inbound media metadata from `/` and download the returned Dualhook `//content` URL with the same bearer key. 6. Ensure template synchronization uses bearer authentication and resubmits only Dualhook's safe `after` or `before` cursor. The published v4.16.2 release needs both changes; post-release Chatwoot source already uses bearer authentication and an `after` cursor for routine synchronization, but still needs its v14.0 path normalized. Never allow an absolute `paging.next` Graph URL. 7. Validate the configured Phone Number ID and WABA ID with bounded single-object reads. Do not call the WABA `/phone_numbers` collection. 8. Make health calls provider-aware, bearer-authenticated, and limited to fields supported by the runtime. 9. Adapt Chatwoot CSAT template create, status, and delete operations to the v25.0 template routes. 10. Skip Chatwoot-managed phone registration/deregistration, app subscription, webhook callback override, callback clearing, and Embedded Signup token exchange. Those remain Dualhook control-plane operations. 11. Keep WhatsApp Calling disabled unless every route used by the deployed Chatwoot version has been separately certified. Do not use the global `WHATSAPP_CLOUD_BASE_URL` as the production solution, especially when the installation also hosts inboxes that legitimately use customer-owned Meta tokens. Why the Base URL Alone Is Not Enough[](https://dualhook.com/docs/guide-chatwoot-webhook#why-the-base-url-alone-is-not-enough) ------------------------------------------------------------------------------------------------------------------------------ The published Chatwoot v4.16.2 release contains a global `WHATSAPP_CLOUD_BASE_URL`, but its WhatsApp implementation is not a single interchangeable Graph client. Chatwoot's `develop` branch changed routine template synchronization shortly after the release without changing the application version string, so audit the exact commit you deploy: | Chatwoot behavior | Dualhook requirement | Result without an adapter | | --- | --- | --- | | Text/template/interactive sends use v13.0; attachments use v24.0 | Exact v25.0 routes | Requests are rejected or misrouted. | | Inbox validation uses v14.0 and puts `access_token` in the URL. The published v4.16.2 tag also does this for routine template sync. | Bearer-only `dh_live_...` | Validation is incompatible and the key may enter URL logs. | | The published v4.16.2 tag follows an absolute template `paging.next` URL. Post-release source uses the `after` cursor safely. | Safe cursor pagination only | The release tag needs adaptation; newer source still needs its template path normalized to v25.0. | | Inbox validation calls `//phone_numbers` | Bounded Phone Number ID and WABA reads | The collection is outside the runtime allowlist. | | Health uses a hardcoded `graph.facebook.com` base and query token | Dualhook v25.0 plus bearer auth | Health calls still go to Meta. | | Manual inbox creation attempts registration and webhook setup | Dualhook owns provisioning and Webhook Override | Setup invokes unsupported control-plane routes. | These behaviors are visible in Chatwoot's official published v4.16.2 source for the [WhatsApp Cloud provider](https://github.com/chatwoot/chatwoot/blob/v4.16.2/app/services/whatsapp/providers/whatsapp_cloud_service.rb) , [health service](https://github.com/chatwoot/chatwoot/blob/v4.16.2/app/services/whatsapp/health_service.rb) , [WhatsApp channel model](https://github.com/chatwoot/chatwoot/blob/v4.16.2/app/models/channel/whatsapp.rb) , and [webhook setup service](https://github.com/chatwoot/chatwoot/blob/v4.16.2/app/services/whatsapp/webhook_setup_service.rb) . The later [template-management-token change](https://github.com/chatwoot/chatwoot/commit/59eac9a7c5b4ccc521876d03ffaf72c95e65746c) moved routine template synchronization to bearer authentication and cursor-only pagination, but it did not fix Dualhook inbox validation, API-version fragmentation, hardcoded health/setup clients, or lifecycle mutations. Inbound Webhooks[](https://dualhook.com/docs/guide-chatwoot-webhook#inbound-webhooks) -------------------------------------------------------------------------------------- The outbound credential migration does not require an inbound routing change. Meta can continue sending WhatsApp message and status events directly to the existing Chatwoot callback through Dualhook's Webhook Override: https:///webhooks/whatsapp/ Keep the callback URL, display phone number, Phone Number ID, and verification token unchanged during an outbound migration. Do not delete and recreate the inbox merely to change its outbound transport. There is a webhook-authenticity limitation to understand. In Chatwoot v4.16.2, the [WhatsApp webhook controller](https://github.com/chatwoot/chatwoot/blob/v4.16.2/app/controllers/webhooks/whatsapp_controller.rb) requires `X-Hub-Signature-256` verification for an Embedded Signup inbox or when an applicable app secret is available. A manual WhatsApp Cloud inbox without an app secret can skip POST signature verification. Dualhook will not reveal its Meta app secret as a workaround. Use TLS, an unguessable verification token, rate limits, request-size limits, idempotency, and event monitoring, and account for this gap in your risk review. Feature Support[](https://dualhook.com/docs/guide-chatwoot-webhook#feature-support) ------------------------------------------------------------------------------------ | Configuration | Inbound and statuses | Outbound replies | Media | Template sync | Native health | Existing inbox/history | | --- | --- | --- | --- | --- | --- | --- | | Customer-owned Meta app with native Chatwoot | Yes | Yes | Yes | Yes | Yes | Yes | | Chatwoot Cloud native inbox with `dh_live_...` | Inbound may continue | No | No | No | No | Preserved but partially broken | | Self-hosted with only `WHATSAPP_CLOUD_BASE_URL` changed | Inbound may continue | Not reliable | No | No | No | Yes | | Self-hosted per-inbox Dualhook adapter | Yes | Yes | Yes | Yes | Yes, after adaptation | Yes | | API inbox plus complete bridge | If implemented | If implemented | If implemented | Must be implemented | Must be implemented | Usually a new inbox | Safe Migration Order[](https://dualhook.com/docs/guide-chatwoot-webhook#safe-migration-order) ---------------------------------------------------------------------------------------------- For a self-hosted adapter or an API-channel bridge: 1. Record and back up the existing inbox/channel configuration, IDs, webhook URL, verification token, database, and deployment configuration. 2. Build and test the replacement transport before changing any production credential or callback. 3. Add tests that fail if a Dualhook inbox contacts `graph.facebook.com`, sends `access_token` or `appsecret_proof`, follows an absolute pagination URL, or invokes registration/webhook-management routes. 4. Keep the existing Dualhook Webhook Override unchanged. 5. For a self-hosted adapter, mark the existing inbox as Dualhook-managed and only then replace its outbound credential with the connection-scoped key. 6. Test inbound and outbound text, reply context, interactive messages, template sends, multi-page template sync, inbound and outbound media, delivered/read/ failed statuses, and health. 7. Revoke the previous outbound credential only after all tests succeed and no queued job or campaign still depends on it. Security and Upgrades[](https://dualhook.com/docs/guide-chatwoot-webhook#security-and-upgrades) ------------------------------------------------------------------------------------------------ * Store `dh_live_...` in a server-side secret manager. Chatwoot provider configuration, database replicas, backups, Rails consoles, logs, and support exports must all be treated as secret-bearing systems. * Never send the key to `graph.facebook.com`, put it in a URL, expose it to browser code, or share it in screenshots or support messages. * Never request Dualhook's Meta token, Meta app secret, or generated App Secret Proof. Dualhook injects those internally. * Re-audit the adapter on every Chatwoot upgrade. Search changed WhatsApp code for `graph.facebook.com`, `access_token`, API-version strings, `paging.next`, setup/ teardown calls, media, health, templates, CSAT, and voice features. * Pin the Chatwoot release until the adapter's contract tests pass against a Dualhook test connection and customer-owned Meta inboxes still call Meta. Sources[](https://dualhook.com/docs/guide-chatwoot-webhook#sources) -------------------------------------------------------------------- This compatibility review was checked against the published Chatwoot v4.16.2 release, marked as the [latest Chatwoot release](https://github.com/chatwoot/chatwoot/releases/tag/v4.16.2) on July 31, 2026, plus post-release source changes through commit `bc7ae88d`. The published release tag points to `70e284a`; repository HEAD still reported application version 4.16.2 while containing later changes. Always identify the exact commit as well as the displayed version. Official sources: * [Chatwoot WhatsApp setup guide](https://www.chatwoot.com/hc/user-guide/articles/1677832735-how-to-setup-a-whats_app-channel) * [Chatwoot Embedded Signup-to-manual migration](https://www.chatwoot.com/hc/user-guide/articles/1783938358-migrate-a-whats_app-embedded-signup-inbox-to-manual-setup) * [Chatwoot API inbox guide](https://www.chatwoot.com/hc/user-guide/articles/1677839703-how-to-create-an-api-channel-inbox) * [Chatwoot v4.16.2 WhatsApp Cloud provider](https://github.com/chatwoot/chatwoot/blob/v4.16.2/app/services/whatsapp/providers/whatsapp_cloud_service.rb) * [Chatwoot v4.16.2 health service](https://github.com/chatwoot/chatwoot/blob/v4.16.2/app/services/whatsapp/health_service.rb) * [Chatwoot v4.16.2 channel lifecycle](https://github.com/chatwoot/chatwoot/blob/v4.16.2/app/models/channel/whatsapp.rb) * [Chatwoot v4.16.2 webhook setup](https://github.com/chatwoot/chatwoot/blob/v4.16.2/app/services/whatsapp/webhook_setup_service.rb) * [Chatwoot v4.16.2 webhook teardown](https://github.com/chatwoot/chatwoot/blob/v4.16.2/app/services/whatsapp/webhook_teardown_service.rb) * [Chatwoot v4.16.2 inbound webhook controller](https://github.com/chatwoot/chatwoot/blob/v4.16.2/app/controllers/webhooks/whatsapp_controller.rb) * [Post-release template synchronization change](https://github.com/chatwoot/chatwoot/commit/59eac9a7c5b4ccc521876d03ffaf72c95e65746c) * [Meta WhatsApp Cloud API overview](https://developers.facebook.com/docs/whatsapp/cloud-api/overview) * [Meta Graph Webhooks reference](https://developers.facebook.com/docs/graph-api/webhooks) * [Dualhook Runtime API Authentication](https://dualhook.com/docs/runtime-api) * [Dualhook Runtime API Endpoint Reference](https://dualhook.com/docs/runtime-api-reference) Related ------- * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Runtime API](https://dualhook.com/docs/runtime-api) Use, protect, rotate, and scope dh\_live\_ keys for Dualhook's allowlisted Graph-compatible WhatsApp runtime. * [Runtime API Endpoint Reference](https://dualhook.com/docs/runtime-api-reference) Graph-compatible message, media, template, health, profile, phone-settings, and QR endpoints for connection-scoped dh\_live\_ keys. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. * [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) How to send template messages via Cloud API with variables, media headers, and URL buttons. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Laravel WhatsApp Webhook Integration with Dualhook | Dualhook Browse Docs Laravel Webhook Integration =========================== Build a Laravel webhook controller for WhatsApp messages routed through Dualhook. Build a Laravel controller that receives WhatsApp messages and status updates directly from Meta, with Dualhook handling the Meta-side configuration. You will verify the GET handshake, validate inbound payload shape without a Meta app secret, dispatch messages to a job, and send a template reply with a connection-scoped `dh_live_` key. The same structure works for Symfony or plain PHP. Prerequisites[](https://dualhook.com/docs/guide-laravel-webhook#prerequisites) ------------------------------------------------------------------------------- * Laravel 10+ with PHP 8.1+ * A WhatsApp Business Account connected through [Dualhook](https://dualhook.com/docs/getting-started) * [Webhook Override](https://dualhook.com/docs/webhook-override) configured so Meta delivers messages directly to your server * Your webhook verify token (for Meta's GET handshake — set during connection setup in Dualhook) * A connection-scoped Dualhook API key for outbound sends Add these to your `.env`: WEBHOOK_VERIFY_TOKEN=your_verify_token DUALHOOK_API_KEY=dh_live_your_key WHATSAPP_PHONE_NUMBER_ID=your_phone_number_id WHATSAPP_WABA_ID=your_waba_id > **No `META_APP_SECRET`?** In the Embedded Signup flow `X-Hub-Signature-256` is signed by Dualhook's Meta app and isn't customer-verifiable — validate inbound webhook payload shape instead. See [Messaging Webhook → Inbound Webhook POST Validation](https://dualhook.com/docs/messaging-webhook#signature-validation-post) > . Create a Webhook Controller[](https://dualhook.com/docs/guide-laravel-webhook#create-a-webhook-controller) ----------------------------------------------------------------------------------------------------------- Create a controller that handles both the GET verification challenge and POST webhook events. Routes in `routes/api.php` do not have CSRF protection, so no additional exclusion is needed. Meta requires a quick 200 response (within ~10 seconds) on POST webhooks and will retry on non-2xx responses. For production workloads, dispatch the processing to a queue job and return 200 immediately. // routes/api.php Route::get('/webhook', [WebhookController::class, 'verify']); Route::post('/webhook', [WebhookController::class, 'handle']); // app/Http/Controllers/WebhookController.php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; class WebhookController extends Controller { public function verify(Request $request) { // PHP converts dots in query params to underscores in $_GET, // so hub.mode becomes hub_mode automatically. $mode = $request->query('hub_mode'); $token = $request->query('hub_verify_token'); $challenge = $request->query('hub_challenge'); if ($mode === 'subscribe' && $token === config('services.whatsapp.verify_token')) { return response($challenge, 200); } return response('Forbidden', 403); } public function handle(Request $request) { if (! $this->isExpectedPayload($request->all())) { return response('Unauthorized', 401); } // Respond 200 immediately to prevent Meta retries. // For production, dispatch to a queue job instead of inline processing. $this->processWebhook($request->all()); return response('OK', 200); } } Add the config reference: // config/services.php 'whatsapp' => [\ 'verify_token' => env('WEBHOOK_VERIFY_TOKEN'),\ 'api_key' => env('DUALHOOK_API_KEY'),\ 'phone_number_id' => env('WHATSAPP_PHONE_NUMBER_ID'),\ 'waba_id' => env('WHATSAPP_WABA_ID'),\ ], Validate the Webhook Payload[](https://dualhook.com/docs/guide-laravel-webhook#validate-the-webhook-payload) ------------------------------------------------------------------------------------------------------------- Validate the envelope, WABA ID, and `phone_number_id` against the values you stored for this connection, and serve the endpoint on a private, high-entropy URL path. // In WebhookController private function isExpectedPayload(array $payload): bool { if (($payload['object'] ?? '') !== 'whatsapp_business_account') { return false; } $expectedWaba = config('services.whatsapp.waba_id'); $expectedPhone = config('services.whatsapp.phone_number_id'); foreach ($payload['entry'] ?? [] as $entry) { if (($entry['id'] ?? '') !== $expectedWaba) { return false; } if (empty($entry['changes']) || ! is_array($entry['changes'])) { return false; } foreach ($entry['changes'] as $change) { if (($change['field'] ?? '') !== 'messages') { return false; } $phoneNumberId = $change['value']['metadata']['phone_number_id'] ?? null; if ($phoneNumberId !== $expectedPhone) { return false; } } } return true; } Handle Incoming Messages[](https://dualhook.com/docs/guide-laravel-webhook#handle-incoming-messages) ----------------------------------------------------------------------------------------------------- Parse the webhook payload to extract messages and statuses. Meta sends changes grouped by phone number. // In WebhookController private function processWebhook(array $payload): void { if (($payload['object'] ?? '') !== 'whatsapp_business_account') { return; } foreach ($payload['entry'] ?? [] as $entry) { foreach ($entry['changes'] ?? [] as $change) { if ($change['field'] !== 'messages') { continue; } $value = $change['value']; $phoneNumberId = $value['metadata']['phone_number_id'] ?? null; // Handle incoming messages foreach ($value['messages'] ?? [] as $message) { Log::info('WhatsApp message received', [\ 'from' => $message['from'],\ 'type' => $message['type'],\ 'text' => $message['text']['body'] ?? null,\ ]); // Dispatch to your application logic } // Handle delivery statuses foreach ($value['statuses'] ?? [] as $status) { Log::info('WhatsApp status update', [\ 'message_id' => $status['id'],\ 'status' => $status['status'],\ ]); } } } } For production, dispatch webhook processing to a [Laravel queue job](https://laravel.com/docs/queues) rather than handling it inline. This keeps response times fast and prevents timeouts. Send a Template Message[](https://dualhook.com/docs/guide-laravel-webhook#send-a-template-message) --------------------------------------------------------------------------------------------------- Use Laravel's HTTP client to send template messages through Dualhook's allowlisted runtime. Inbound webhooks still arrive directly from Meta. The `to` field must be in E.164 format (e.g. `+14155552671`). WhatsApp rejects numbers that are not E.164-formatted. // app/Services/WhatsAppService.php namespace App\Services; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; class WhatsAppService { public function sendTemplate( string $to, string $templateName, string $languageCode = 'en' ): array { $config = config('services.whatsapp'); $response = Http::withToken($config['api_key']) ->post( "https://api.dualhook.com/v25.0/{$config['phone_number_id']}/messages", [\ 'messaging_product' => 'whatsapp',\ 'to' => $to,\ 'type' => 'template',\ 'template' => [\ 'name' => $templateName,\ 'language' => ['code' => $languageCode],\ ],\ ] ); if ($response->failed()) { Log::error('WhatsApp send failed', [\ 'error' => $response->json('error'),\ ]); } return $response->json(); } } For templates with variables and media headers, see the [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) documentation. Test with Dualhook[](https://dualhook.com/docs/guide-laravel-webhook#test-with-dualhook) ----------------------------------------------------------------------------------------- 1. Connect your number through [Embedded Signup](https://dualhook.com/docs/embedded-signup) — Dualhook configures the webhook URL via Meta's Webhook Override automatically. 2. Set your server's URL as the webhook endpoint in the Dualhook dashboard. For local development, use a tunnel tool like ngrok. 3. Send a test message to your WhatsApp number. The message webhook arrives directly at your Laravel endpoint from Meta. 4. Check Dualhook's [webhook activity logs](https://dualhook.com/docs/basics-overview) to verify management events are being received for operational monitoring. Inbound messages flow from Meta directly to your server. Outbound payloads transit Dualhook's runtime without content persistence or caching. Related ------- * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. * [The Basics Overview](https://dualhook.com/docs/basics-overview) Base URL, authentication, message send payloads, and webhook subscription basics. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Calling SIP Configuration: TLS, Digest Auth, SDES vs DTLS | Dualhook Browse Docs SIP Configuration ================= Use SIP signaling instead of Graph API + webhooks for WhatsApp calling. TLS certificate requirements, digest authentication, business and user initiated SIP flows, SDES vs DTLS for SRTP, custom SIP headers. What SIP Mode Is[](https://dualhook.com/docs/calling-sip#what-sip-mode-is) --------------------------------------------------------------------------- **Session Initiation Protocol (SIP)** is the standard signaling protocol for VoIP. WhatsApp Business Calling can use SIP for call signaling instead of Graph API endpoints + `calls` webhooks. When SIP is enabled on a business phone number: * Calling-related Graph API endpoints (`//calls`) **stop working** — return error `131055`. * Calling webhooks (`calls` field) are **not sent**. * Messaging Graph API + webhooks continue to work normally — SIP only intercepts the calling signaling path. Use SIP if you have a SIP-based contact center, IVR, or telephony infrastructure that needs native SIP integration. Otherwise, prefer Graph API + webhooks for simpler operations. Trade-offs vs Graph API + Webhooks[](https://dualhook.com/docs/calling-sip#trade-offs-vs-graph-api--webhooks) -------------------------------------------------------------------------------------------------------------- | | Graph API + Webhooks | SIP | | --- | --- | --- | | Setup complexity | Lower — webhook endpoint + WebRTC stack | Higher — SIP server + TLS certificate + digest auth | | Operational overhead | Lower | Higher (TLS cert lifecycle, SIP server uptime) | | Native fit for SIP-based call centers | Awkward (need translation layer) | Native | | Calling webhooks | Yes | No — SIP messages replace them | | Functional parity | Yes | Yes | | Recommended for | Most setups | Only when SIP is already core infra | Prerequisites[](https://dualhook.com/docs/calling-sip#prerequisites) --------------------------------------------------------------------- * All standard [calling prerequisites](https://dualhook.com/docs/calling#prerequisites) met. * Your app has `whatsapp_business_messaging` for the business phone number — verify by sending a test message before configuring SIP. * Your app mode is **Live**, not Development. * A standards-compliant third-party SIP server that supports **TLS transport** and **digest authentication**. TLS Certificate Requirements[](https://dualhook.com/docs/calling-sip#tls-certificate-requirements) --------------------------------------------------------------------------------------------------- TLS transport is **mandatory** for SIP. Two requirements: 1. **Meta's cert** has subject name `wa.meta.vc`. Your SIP client should validate it. 2. **Your SIP server's cert** must have a subject name covering the SIP domain you configure (e.g. `meta-voip.example.com`). Meta validates it on connection. Meta does **not** support mutual TLS. When Meta acts as a TLS client, your TLS server must **not** request a client certificate. If you do, Meta returns a cert with a random dynamic-host subject that won't pass your validation. Verify your TLS setup before reporting that calls aren't being delivered: openssl s_client -quiet -verify_hostname meta-voip.example.com -connect meta-voip.example.com:5061 A valid cert returns `verify return:1` for each depth and connects cleanly. Hostname mismatch produces `verify error:num=62:hostname mismatch`. Connection refused / timeout means the host isn't listening for TLS on that port. Configure SIP[](https://dualhook.com/docs/calling-sip#configure-sip) --------------------------------------------------------------------- POST //settings { "calling": { "sip": { "status": "ENABLED", "servers": [\ {\ "hostname": "sip.example.com",\ "port": 5061,\ "request_uri_user_params": {\ "tgrp": "wacall",\ "trunk-context": "byoc.example.com"\ }\ }\ ] } } } | Field | Notes | | --- | --- | | `status` | `ENABLED` makes SIP exclusive (no Graph API/webhooks for calling). `DISABLED` is the default. Disabling does NOT clear `servers` — they persist for re-enabling. | | `servers[]` | One server per phone number. Multiple entries in the GET response means different apps configured different servers (we extract the app from the access token). | | `hostname` | TLS-only. Your SIP server hostname. | | `port` | TLS port (default 5061). | | `request_uri_user_params` | Optional key/value pairs added to the user portion of the request URI. Useful for trunk groups (`tgrp`, `trunk-context` per RFC 4904). Max key/value 128 chars each. | To **delete** a SIP server, pass an empty `servers` array. If servers remain afterward, they belong to a different app — disable SIP entirely or use the corresponding app's token to clear them. At least one SIP server (any app) must exist when `sip.status: ENABLED`. Get and Include SIP Password[](https://dualhook.com/docs/calling-sip#get-and-include-sip-password) --------------------------------------------------------------------------------------------------- Meta generates a unique SIP password per (phone number + app) combination. Retrieve it by adding `?include_sip_credentials=true` to the GET settings call: GET //settings?include_sip_credentials=true { "calling": { "sip": { "status": "ENABLED", "servers": [\ {\ "app_id": 123456789012345,\ "hostname": "sip.example.com",\ "sip_user_password": ""\ }\ ] } } } Configure this password on your SIP server so it can respond to digest authentication challenges from Meta. Treat the password as a secret — Dualhook customers should pull it via API and store it themselves; we don't proxy or cache it. Reset SIP Password[](https://dualhook.com/docs/calling-sip#reset-sip-password) ------------------------------------------------------------------------------- There's no dedicated "rotate password" endpoint. To force regeneration: 1. Disable SIP and clear servers (`status: "DISABLED"`, `servers: []`). 2. Re-enable SIP with your server (`status: "ENABLED"`, `servers: [{ "hostname": "..." }]`). 3. Fetch with `include_sip_credentials=true` to read the new password. Make sure you've recorded any old password before doing this — the rotation is irreversible. Business-Initiated SIP Flow[](https://dualhook.com/docs/calling-sip#business-initiated-sip-flow) ------------------------------------------------------------------------------------------------- 1. Send an **initial SIP INVITE** to `wa.meta.vc`. Request URI for calling user `+11234567890`: `sip:+11234567890@wa.meta.vc;transport=tls`. 2. Meta returns **`SIP/2.0 407 Proxy Authentication Required`** with a `WWW-Authenticate` header. 3. Send a **second INVITE** with a proper `Authorization` header per RFC 3261 digest auth — username = your business phone number (normalized), password = Meta-generated SIP password. 4. Meta routes the call to the WhatsApp user. Constraints on your INVITE: * `From` header username: fully normalized business phone number. * `From` header domain: must match the SIP server hostname you configured. * SDP offer: WebRTC-compatible (ICE + DTLS-SRTP + OPUS) — or SDES if configured (see below). If you receive `403 SIP server foo.com from INVITE does not match any SIP server configured for phone number id [ID]`, your `From` header domain doesn't match the SIP server hostname stored in your config. Sub-domains of the configured domain are valid (e.g. `bar.foo.com` matches `foo.com`). User-Initiated SIP Flow[](https://dualhook.com/docs/calling-sip#user-initiated-sip-flow) ----------------------------------------------------------------------------------------- 1. WhatsApp user calls your business number. 2. Meta sends a **SIP INVITE** to your configured SIP server. 3. **Recommended:** challenge with `407 Proxy Authentication Required` (digest auth). Meta resends the INVITE with the auth header. 4. Respond with `200 OK` containing your SDP answer. If you don't challenge (no digest auth), Meta sends the INVITE without auth — security best practice is to challenge. User experience is identical whether you're on SIP or Graph API — the user has no idea which signaling path you use. If your SIP server doesn't receive the INVITE, the most common causes are TLS certificate validation errors, app permission mismatch, or firewall / port reachability issues. Custom SIP Headers[](https://dualhook.com/docs/calling-sip#custom-sip-headers) ------------------------------------------------------------------------------- Meta adds these custom headers to SIP requests: | Header | When | Value | | --- | --- | --- | | `x-wa-meta-call-duration` | SIP BYE from Meta on established calls | Call duration in seconds | | `x-wa-meta-wacid` | SIP INVITE (UIC) and BYE (UIC + BIC) | WhatsApp call ID | | `x-wa-meta-cta-payload` | UIC INVITE when call originated from a [`voice_call` button](https://dualhook.com/docs/calling-call-buttons)
with payload | Business-specified payload | | `x-wa-meta-deeplink-payload` | UIC INVITE when call originated from a `wa.me/call/?biz_payload=...` deep link | Business-specified payload | SDES vs DTLS for SRTP Key Exchange[](https://dualhook.com/docs/calling-sip#sdes-vs-dtls-for-srtp-key-exchange) --------------------------------------------------------------------------------------------------------------- SRTP needs a key-exchange mechanism. Two options: | | DTLS (default) | SDES | | --- | --- | --- | | Industry-standard | Yes | Older standard | | Requires ICE/DTLS handshake | Yes | No | | Call setup time | Slower (handshake completes after SDP exchange) | Faster (key in SDP, sent over secure SIP) | | Supports WebRTC stacks | Yes | Yes | | When to prefer | Default for most setups | When using SIP and you want shorter call setup, or to address audio clipping caused by DTLS retry intervals | DTLS retries on a 1s, 2s, 4s backoff — common SIP setups see ~3 seconds of dropped media at call start while waiting for the DTLS client hello. SDES sends the key in the SDP itself, so SRTP can flow immediately after SDP exchange. See [Calling Troubleshooting → Audio Clipping](https://dualhook.com/docs/calling-troubleshooting#audio-clipping) for the full audio-clipping mitigation menu. Configure SRTP Key Exchange[](https://dualhook.com/docs/calling-sip#configure-srtp-key-exchange) ------------------------------------------------------------------------------------------------- POST //settings { "calling": { "srtp_key_exchange_protocol": "SDES" } } Possible values: `DTLS` (default) or `SDES`. To check current setting: GET //settings If the field has not been explicitly set, it's not returned. Note: Meta still expects the business side to send the maiden SRTP packet for both UIC and BIC. Common SIP Errors[](https://dualhook.com/docs/calling-sip#common-sip-errors) ----------------------------------------------------------------------------- | SIP status / error | Meaning | Fix | | --- | --- | --- | | `400 Asset not found` | `From` header phone number is not a registered business number | Check the number and resend | | `403 SIP server X from INVITE does not match...` | `From` header domain doesn't match configured SIP server hostname | Update SIP config or `From` header | | `403 No Approved Call Permission Found` | No permission from recipient (or recipient not on WhatsApp) | Get permission first ([Call Permissions](https://dualhook.com/docs/calling-permissions)
) | | `403 The app [APP_ID] configured for SIP server X is not authorized for phone number id [ID]` | App used to configure SIP doesn't have `whatsapp_business_messaging` on the number | Reconfigure with the right app | | `403 Business Initiated Connected Call Per Day Limit Hit` | 5 connected BICs in 24h reached | Wait or check pricing tier | | `404 SIP INVITEs using IP in request URI are not allowed` | Don't use IP literal in request URI | Use the correct request URI (see flow examples) | | `407 Proxy Authentication Required` | Standard digest auth challenge from Meta | Resend with `Authorization` header | | `408 RTP Timeout` | No media received for too long | See [Audio Clipping & Media Issues](https://dualhook.com/docs/calling-troubleshooting#audio-clipping) | | `480 Temporarily Unavailable` | User unreachable / didn't answer | Try later; unanswered calls degrade permission | | `486 Busy Here` | User declined the call | Try later; rejections degrade permission | | `487 Request Terminated` | You sent SIP CANCEL before Meta responded | Expected — your CANCEL worked | | `503 Service Unavailable` | Generic internal error | Retry later or contact Meta support | Full error reference and Graph-API-side errors on [Calling Troubleshooting](https://dualhook.com/docs/calling-troubleshooting) . SIP FAQ[](https://dualhook.com/docs/calling-sip#sip-faq) --------------------------------------------------------- **Why is user-initiated call disconnecting immediately after enabling SIP?** Most likely TLS certificate validation error. Run the `openssl s_client` check above. **Why am I not getting SIP requests when expected?** Common causes: TLS cert validation error, SIP not actually enabled (verify with GET settings), app that configured SIP lacks `whatsapp_business_messaging`, network/firewall blocking Meta's IP ranges, or SIP server not listening on the configured port. Verify TCP reachability with `nc -zvw2 -G 2 `. **Why is my SIP TERMINATE not hanging up the user?** Usually TLS handshake failure when your SIP server tries to establish TLS to Meta's. Capture SIP traffic with pcap or check SIP server logs. **Why does my SIP server keep responding 401 Unauthorized for UIC?** Meta supports digest auth. After your 401, Meta resends the INVITE with `Authorization`. Confirm your SIP server is configured with username = (normalized) business phone number, password = Meta-generated password. Or disable digest auth on your SIP server (not recommended). **Why is my SIP server responding 488 Not Acceptable Here?** Likely your SIP server doesn't support WebRTC ICE. Switch the business phone number to use SDES instead of DTLS. **Do I need to SIP REGISTER the business phone number to Meta?** No. REGISTER requests fail with `403 Forbidden`. Meta's SIP server only owns the `meta.vc` domain (where WhatsApp consumers live). Your business numbers belong to the SIP domain you configure. **Does Meta support SIP re-INVITEs?** No. Returns `500 Internal Server Error`. **SIP vs Graph API — is one functionally better?** No, they have parity. Pick based on your existing infrastructure. **If I use SIP, do I still need webhooks?** Yes — but only for messaging and other non-calling events. SIP only covers call signaling. **Does Meta have an approved list of SIP vendors / SBCs?** No. Any compatible standards-compliant SIP server works. Related ------- * [Cloud API Calling](https://dualhook.com/docs/calling) Voice calling on WhatsApp Business Cloud API: VoIP via WebRTC or SIP, business-initiated and user-initiated calls, prerequisites, availability, and integration patterns. * [Calling Configuration](https://dualhook.com/docs/calling-configuration) Enable calling on a business phone number, configure call hours and holidays, restrict the call icon by country, set callback permissions, choose audio codecs, listen for settings webhooks. * [Calling Troubleshooting](https://dualhook.com/docs/calling-troubleshooting) WhatsApp Calling errors and fixes: full error code table (138000-138023), SIP-specific status codes, media-flow timeouts, audio clipping causes and mitigations (SDES, pre-accept), quick triage guide. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Authentication Messages | Dualhook Browse Docs Authentication Messages ======================= OTP and verification code delivery via WhatsApp: copy-code, one-tap, and zero-tap templates, linked device security, the 'I didn't request a code' webhook, and PendingIntent deprecation. Scope[](https://dualhook.com/docs/authentication-messages#scope) ----------------------------------------------------------------- Authentication messages are [template](https://dualhook.com/docs/templates) messages used to deliver one-time passcodes (OTP/verification codes). If you deliver login or verification codes over WhatsApp, use authentication templates — not [utility](https://dualhook.com/docs/utility-messages) or [marketing](https://dualhook.com/docs/marketing-messages) templates. Authentication Pages in This Section[](https://dualhook.com/docs/authentication-messages#authentication-pages-in-this-section) ------------------------------------------------------------------------------------------------------------------------------- * [OTP Handshake & Android SDK](https://dualhook.com/docs/authentication-otp-handshake) — handshake mechanics, OTP Android SDK, eligibility checks, request\_id validation, error codes * [Bulk Management](https://dualhook.com/docs/authentication-bulk-management) — `upsert_message_templates` (multi-language create/update) and `message_template_previews` (generate preview text in any language) * [Authentication Best Practices](https://dualhook.com/docs/authentication-best-practices) — security (recycled phone numbers, identity hash binding) and UX (opt-in, isWhatsAppInstalled checks, multi-app support) Preset Text Structure[](https://dualhook.com/docs/authentication-messages#preset-text-structure) ------------------------------------------------------------------------------------------------- Authentication templates use **fixed, non-customizable preset text**: * ` is your verification code.` (always shown) * Optional security disclaimer: `For your security, do not share this code.` (toggle via `add_security_recommendation: true`) * Optional expiration warning: `This code expires in minutes.` (toggle via `code_expiration_minutes` integer) You cannot edit the preset text. The button label can be customized with `text` / `autofill_text`, but omitting those fields is usually safer because Meta applies the preset localized label for the template language. Button Types: Copy Code vs One-Tap vs Zero-Tap[](https://dualhook.com/docs/authentication-messages#button-types-copy-code-vs-one-tap-vs-zero-tap) -------------------------------------------------------------------------------------------------------------------------------------------------- | Type | UX | Platform | Requires app integration | Falls back to | | --- | --- | --- | --- | --- | | `copy_code` | Tap → code copied to clipboard, user pastes into your app | All platforms | No | — | | `one_tap` | Tap → autofills code into your app | Android only | Yes — handshake + Activity intent | `copy_code` button if eligibility check fails | | `zero_tap` | No tap — code broadcast directly to your app | Android only | Yes — handshake + BroadcastReceiver + `zero_tap_terms_accepted: true` | `one_tap` first, then `copy_code` | **Recommendation:** Start with `copy_code` (universal), upgrade to `one_tap` (Android autofill) or `zero_tap` (silent delivery) once you have an Android app and want lower OTP-friction conversion. For one-tap and zero-tap, the handshake and SDK details are on a dedicated page: [OTP Handshake & Android SDK](https://dualhook.com/docs/authentication-otp-handshake) . Linked Device Security[](https://dualhook.com/docs/authentication-messages#linked-device-security) --------------------------------------------------------------------------------------------------- Authentication messages now feature **linked device security**: messages are only delivered to a user's primary WhatsApp device. On linked devices (WhatsApp Web, Desktop, secondary phones), the message body is masked with a prompt instructing the user to view it on their primary device. This feature is enabled by default, requires no code changes, and **cannot be configured or disabled**. It applies only to Cloud API authentication messages. Requirements and Constraints[](https://dualhook.com/docs/authentication-messages#requirements-and-constraints) --------------------------------------------------------------------------------------------------------------- * Use template category `AUTHENTICATION`. * Use the preset authentication text structure and OTP button model. * **URLs, media, and emojis are not supported** in authentication template content. * OTP delivery should target users who explicitly requested the code. Common operational prerequisites: * [Business verification](https://dualhook.com/docs/meta-business-verification) completed. * Sufficient [messaging limit tier](https://dualhook.com/docs/capacity-quality-limits) for authentication use. Template Building Blocks[](https://dualhook.com/docs/authentication-messages#template-building-blocks) ------------------------------------------------------------------------------------------------------- Authentication templates are assembled from three component types: * `body` — fixed auth text + optional `add_security_recommendation: true` * `footer` — optional `code_expiration_minutes` (integer, 1–90) * `buttons` — single OTP button (`copy_code`, `one_tap`, or `zero_tap`) You cannot add a header, additional buttons, or any other component to an authentication template. Time-To-Live (TTL) and Code Expiration[](https://dualhook.com/docs/authentication-messages#time-to-live-ttl-and-code-expiration) --------------------------------------------------------------------------------------------------------------------------------- Two related but distinct settings: | Property | Where set | What it controls | | --- | --- | --- | | `message_send_ttl_seconds` | Template root | Maximum delivery retry window — Meta will keep retrying delivery for this many seconds before giving up. Useful when network or app-state issues delay delivery. | | `code_expiration_minutes` | `footer` component | The expiration shown in the message text and the timeframe after which the OTP button becomes disabled. Defaults to 10 minutes if omitted. Min 1, max 90. | Keep TTL short so users don't receive expired OTPs. A typical configuration: `message_send_ttl_seconds: 60` paired with `code_expiration_minutes: 5`. Copy-Code Templates[](https://dualhook.com/docs/authentication-messages#copy-code-templates) --------------------------------------------------------------------------------------------- Copy-code templates are the most compatible OTP option. They do not require any Android handshake or signing integration, work on iOS, and never fall back further than themselves. { "name": "authentication_code_copy_code_button", "language": "en_US", "category": "authentication", "message_send_ttl_seconds": 60, "components": [\ { "type": "body", "add_security_recommendation": true },\ { "type": "footer", "code_expiration_minutes": 5 },\ {\ "type": "buttons",\ "buttons": [\ { "type": "otp", "otp_type": "copy_code" }\ ]\ }\ ] } Omit the copy-code button `text` to let Meta use the preset localized label for the template language, such as "Copy code" for English. Note: in the create request the button `type` is `otp`, but the saved template stores it as `url`. A `GET` on the created template will return `url` — that is expected. One-Tap Templates[](https://dualhook.com/docs/authentication-messages#one-tap-templates) ----------------------------------------------------------------------------------------- One-tap templates autofill the OTP into your Android app. If the message fails its eligibility check, WhatsApp transparently falls back to a copy-code button. { "name": "authentication_code_autofill_button", "language": "en_US", "category": "authentication", "message_send_ttl_seconds": 60, "components": [\ { "type": "body", "add_security_recommendation": true },\ { "type": "footer", "code_expiration_minutes": 5 },\ {\ "type": "buttons",\ "buttons": [\ {\ "type": "otp",\ "otp_type": "one_tap",\ "supported_apps": [\ { "package_name": "com.example.myapplication", "signature_hash": "K8a/AINcGX7" }\ ]\ }\ ]\ }\ ] } `supported_apps` lets you list up to **5** package\_name + signature\_hash pairs (different app builds, different platforms, etc.). Eligibility check requires the actual installed app's signature to match one of these. See [OTP Handshake & Android SDK](https://dualhook.com/docs/authentication-otp-handshake) for the handshake protocol. The legacy form — defining `package_name` and `signature_hash` directly on the button (without `supported_apps`) — works on Graph API v20.0 and older, but is deprecated and should not be used in new code. Zero-Tap Templates[](https://dualhook.com/docs/authentication-messages#zero-tap-templates) ------------------------------------------------------------------------------------------- Zero-tap delivers the OTP silently to your app via Android broadcast receiver. The user does not see (or need to tap) any button. { "name": "zero_tap_auth_template", "language": "en_US", "category": "authentication", "message_send_ttl_seconds": 60, "components": [\ { "type": "body", "add_security_recommendation": true },\ { "type": "footer", "code_expiration_minutes": 5 },\ {\ "type": "buttons",\ "buttons": [\ {\ "type": "otp",\ "otp_type": "zero_tap",\ "zero_tap_terms_accepted": true,\ "supported_apps": [\ { "package_name": "com.example.myapplication", "signature_hash": "K8a/AINcGX7" }\ ]\ }\ ]\ }\ ] } `zero_tap_terms_accepted: true` is **required** — it acknowledges that you understand zero-tap is subject to the WhatsApp Business Terms of Service and that your customers expect codes to be auto-filled silently. Setting it to `false` blocks template creation. You also include the `text` and `autofill_text` button properties even though zero-tap may never display either — the eligibility-failure fallback chain (zero-tap → one-tap → copy-code) needs them. Sending an Authentication Template[](https://dualhook.com/docs/authentication-messages#sending-an-authentication-template) --------------------------------------------------------------------------------------------------------------------------- Provide the OTP value in both the body parameter and the button parameter (the value must appear twice in the payload): { "messaging_product": "whatsapp", "to": "12015550123", "type": "template", "template": { "name": "verification_code", "language": { "code": "en_US" }, "components": [\ {\ "type": "body",\ "parameters": [\ { "type": "text", "text": "839201" }\ ]\ },\ {\ "type": "button",\ "sub_type": "url",\ "index": 0,\ "parameters": [\ { "type": "text", "text": "839201" }\ ]\ }\ ] } } The OTP code maximum length is **15 characters**. For one-tap and zero-tap, you must initiate the [handshake](https://dualhook.com/docs/authentication-otp-handshake) **before** calling the send endpoint, otherwise the eligibility check fails and WhatsApp falls back to copy-code. Dualhook supports [template management](https://dualhook.com/docs/templates) and operational monitoring. Authentication delivery is still controlled by Meta policies, device eligibility, and app integration readiness. Dualhook does not bypass handshake or signing requirements. "I didn't request a code" Webhook[](https://dualhook.com/docs/authentication-messages#i-didnt-request-a-code-webhook) ---------------------------------------------------------------------------------------------------------------------- When a user receives an OTP they didn't request, they can tap an **"I didn't request a code"** button (currently in beta, rolling out incrementally). This triggers a `messages` webhook with `type: "button"` and payload `DID_NOT_REQUEST_CODE`: { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "value": {\ "messaging_product": "whatsapp",\ "metadata": {\ "display_phone_number": "15550001234",\ "phone_number_id": "123456789012345"\ },\ "messages": [\ {\ "context": {\ "from": "12015550123",\ "id": "wamid.HBgLMTIxMTU1NTE0NTYVAgARGBJDMDEyMTFDNTE5NkFCOUU3QTEA"\ },\ "from": "12015550123",\ "id": "wamid.HBgLMTIxMTU1NTE0NTYVAgASGCBBQ0I3MjdCNUUzMTE0QjhFQkM4RkQ4MEU3QkE0MUNEMgA=",\ "timestamp": "1753919111",\ "type": "button",\ "button": {\ "payload": "DID_NOT_REQUEST_CODE",\ "text": "I didn't request a code"\ }\ }\ ]\ },\ "field": "messages"\ }\ ]\ }\ ] } This webhook arrives on the `messages` field, so in Dualhook setups it goes directly to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) — Dualhook does not ingest it. If you receive this webhook for a recipient, treat it as a strong negative signal: that user did not initiate the code request, which can indicate credential stuffing or unauthorized account access on your side. Lock the account, invalidate active sessions, and do not auto-resend codes. PendingIntent Deprecation (Oct 15, 2026)[](https://dualhook.com/docs/authentication-messages#pendingintent-deprecation-oct-15-2026) ------------------------------------------------------------------------------------------------------------------------------------ The PendingIntent-based handshake method for one-tap and zero-tap will be deprecated on **October 15, 2026** (extended from earlier deadline). If you currently initiate handshakes or verify app identity via PendingIntent, migrate to the **OTP Android SDK** before that date. The SDK uses request\_id (UUID) validation and is the only supported handshake method going forward. See [OTP Handshake & Android SDK](https://dualhook.com/docs/authentication-otp-handshake) for migration details. International Rates[](https://dualhook.com/docs/authentication-messages#international-rates) --------------------------------------------------------------------------------------------- A separate **authentication-international** rate applies to authentication template messages delivered to users in: Egypt, India, Indonesia, Malaysia, Nigeria, Pakistan, Saudi Arabia, South Africa, and the United Arab Emirates — but only when the sending business is based **outside** that country (per Meta's `primary_business_location` field on the WABA). Eligibility kicks in automatically at >750,000 outside-CSW messages over a moving 30-day window to users in those countries (across all WABAs in the portfolio). Once eligible, Meta provides 30 days notice via email and an `account_update` webhook (`AUTH_INTL_PRICE_ELIGIBILITY_UPDATE`) before the higher rate applies. See [Pricing → Authentication-International Rates](https://dualhook.com/docs/pricing#authentication-international-rates) for the eligibility check and webhook payload. Related ------- * [OTP Handshake & Android SDK](https://dualhook.com/docs/authentication-otp-handshake) How the WhatsApp OTP handshake works for one-tap and zero-tap authentication templates: OTP Android SDK, manifest setup, eligibility checks, multi-app support, error codes, and PendingIntent deprecation. * [Authentication Bulk Management](https://dualhook.com/docs/authentication-bulk-management) Use upsert\_message\_templates to create or update authentication templates across many languages in one call, and use message\_template\_previews to preview localized text before committing. * [Authentication Best Practices](https://dualhook.com/docs/authentication-best-practices) Security and UX best practices for WhatsApp authentication: identity hash binding for recycled phone protection, opt-in collection, isWhatsAppInstalled checks for Android and iOS, multi-app support. * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. * [Utility Messages](https://dualhook.com/docs/utility-messages) Utility template messaging hub: components and buttons, specialty types, the October 2026 CSW billing change, volume tiers, content rules, and recategorization. * [Pricing](https://dualhook.com/docs/pricing) Dualhook subscription plans, what is included, and how pricing relates to Meta messaging fees. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Business-Initiated Calls: API and Webhooks | Dualhook Browse Docs Business-Initiated Calls ======================== Place outbound voice calls to WhatsApp users. POST /calls with action: connect, SDP offer/answer flow, call status webhooks (RINGING/ACCEPTED/REJECTED), terminate webhook with duration, biz\_opaque\_callback\_data tracking. Everything needed to place an outbound WhatsApp voice call from your own backend: the permission prerequisite, the `POST /calls` request with an SDP offer, the Call Connect, status, and terminate webhooks, and the errors that stop a call before it rings. With [Webhook Override](https://dualhook.com/docs/webhook-override) active, Meta delivers the `calls` webhooks to your endpoint directly; Dualhook does not proxy call signalling or media. Overview[](https://dualhook.com/docs/calling-business-initiated#overview) -------------------------------------------------------------------------- Business-initiated calls (BIC) let your business place outbound voice calls to WhatsApp users. The user must have granted you call permission first — see [Call Permissions](https://dualhook.com/docs/calling-permissions) . Permissions can be temporary (7 calendar days) or permanent. BIC is **not available** when the business phone number's country is United States, Canada, Egypt, Vietnam, or Nigeria. The recipient can be in any country where Cloud API is available. Prerequisites[](https://dualhook.com/docs/calling-business-initiated#prerequisites) ------------------------------------------------------------------------------------ * Subscribe your app to the `calls` webhook field. * Calling enabled on the business phone number (see [Calling Configuration](https://dualhook.com/docs/calling-configuration) ). * Approved call permission from the recipient — error `138006` if absent. * Valid payment method on the WABA. UIC is free, but BIC is per-pulse billed (see [Pricing](https://dualhook.com/docs/calling-pricing) ). * Business phone number country in the BIC-supported list. The Four-Step BIC Flow[](https://dualhook.com/docs/calling-business-initiated#the-four-step-bic-flow) ------------------------------------------------------------------------------------------------------ 1. **Obtain permission.** Send a call permission request (free-form or template) and wait for the user to accept, or rely on `callback_permission_status: ENABLED` to auto-grant when the user calls you. See [Call Permissions](https://dualhook.com/docs/calling-permissions) . 2. **POST `/calls`** with `action: connect` and an SDP offer from your WebRTC stack. 3. **Apply the SDP answer** Meta returns via the Call Connect webhook to your WebRTC stack to establish the media session. You'll then receive `RINGING`, `ACCEPTED`, or `REJECTED` status webhooks. 4. **Terminate** when done — either you POST `action: terminate`, or the user hangs up. Either way, a Call Terminate webhook arrives. The `ACCEPTED` status webhook typically arrives **after** the call is already established — Cloud API sends it primarily for auditing, not as a precondition for sending media. Initiate a Call (POST /calls)[](https://dualhook.com/docs/calling-business-initiated#initiate-a-call-post-calls) ----------------------------------------------------------------------------------------------------------------- POST //calls { "messaging_product": "whatsapp", "to": "12015550123", "action": "connect", "session": { "sdp_type": "offer", "sdp": "<>" }, "biz_opaque_callback_data": "0fS5cePMok" } | Field | Notes | | --- | --- | | `to` | Recipient WhatsApp user phone number | | `action` | `connect` for new calls. Other values: `pre_accept`, `accept`, `reject`, `terminate` (used for [user-initiated calls](https://dualhook.com/docs/calling-user-initiated)
) | | `session.sdp_type` | `offer` for BIC | | `session.sdp` | RFC 8866 SDP — **OPUS only**, 48 kHz media clock, 8 kHz DTMF clock, ptime 20ms, single SSRC. See [API Reference → Media Path Guidelines](https://dualhook.com/docs/calling-api-reference#media-path-guidelines) | | `biz_opaque_callback_data` | Optional. Up to 512 chars. Echoed back in subsequent webhooks for tracking | Success response — note the `wacid.` prefix (WhatsApp Call ID, distinct from `wamid.` for messages): { "messaging_product": "whatsapp", "calls": [\ { "id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA" }\ ] } Rate limit: **10,000 new calls per 24 hours per business phone number**. biz\_opaque\_callback\_data[](https://dualhook.com/docs/calling-business-initiated#biz_opaque_callback_data) ------------------------------------------------------------------------------------------------------------- An arbitrary string (up to 512 chars) that you pass into the call and that comes back unmodified in subsequent webhooks. Cloud API doesn't process it — it's purely for your own correlation (tying a call to a CRM record, an outbound campaign, etc.). It's available on: * The Call Connect webhook (BIC) * The Call Terminate webhook (BIC and UIC if you accepted with one) * The Call Status webhook for `RINGING`/`ACCEPTED`/`REJECTED` Set it on `POST /calls` with `action: connect` (BIC) or `action: accept` (UIC). It can't be added later. Terminate a Call[](https://dualhook.com/docs/calling-business-initiated#terminate-a-call) ------------------------------------------------------------------------------------------ POST //calls { "messaging_product": "whatsapp", "call_id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA", "action": "terminate" } Always call `terminate` even if you sent an RTCP BYE in the media path — this ensures accurate billing. You don't need to call it when the user hangs up; you'll just receive the Call Terminate webhook. Success response: { "success": true } Call Connect Webhook (SDP Answer)[](https://dualhook.com/docs/calling-business-initiated#call-connect-webhook-sdp-answer) -------------------------------------------------------------------------------------------------------------------------- Arrives near-real-time after a successful `POST /calls connect`. Apply the SDP answer to your WebRTC stack to start media flow. { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "calls",\ "value": {\ "messaging_product": "whatsapp",\ "metadata": {\ "display_phone_number": "15550001234",\ "phone_number_id": "123456789012345"\ },\ "calls": [\ {\ "id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA",\ "to": "12015550123",\ "from": "15550001234",\ "event": "connect",\ "direction": "BUSINESS_INITIATED",\ "timestamp": "1749196895",\ "biz_opaque_callback_data": "0fS5cePMok",\ "session": {\ "sdp_type": "answer",\ "sdp": "<>"\ }\ }\ ]\ }\ }\ ]\ }\ ] } Call Status Webhook (RINGING / ACCEPTED / REJECTED)[](https://dualhook.com/docs/calling-business-initiated#call-status-webhook-ringing--accepted--rejected) ------------------------------------------------------------------------------------------------------------------------------------------------------------ Mirrors the messaging-status webhook envelope but with `type: "call"`: { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "calls",\ "value": {\ "messaging_product": "whatsapp",\ "metadata": {\ "display_phone_number": "15550001234",\ "phone_number_id": "123456789012345"\ },\ "statuses": [\ {\ "id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA",\ "type": "call",\ "status": "ACCEPTED",\ "timestamp": "1749197000",\ "recipient_id": "12015550123",\ "biz_opaque_callback_data": "0fS5cePMok"\ }\ ]\ }\ }\ ]\ }\ ] } `status` is one of `RINGING`, `ACCEPTED`, `REJECTED`. A `REJECTED` status is followed by a Call Terminate webhook. `ACCEPTED` typically arrives after media is already flowing. Call Terminate Webhook[](https://dualhook.com/docs/calling-business-initiated#call-terminate-webhook) ------------------------------------------------------------------------------------------------------ Arrives whenever the call ends — user hangs up, you call `action: terminate` or `action: reject`, network failure, etc. { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "calls",\ "value": {\ "messaging_product": "whatsapp",\ "metadata": {\ "display_phone_number": "15550001234",\ "phone_number_id": "123456789012345"\ },\ "calls": [\ {\ "id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA",\ "to": "12015550123",\ "from": "15550001234",\ "event": "terminate",\ "direction": "BUSINESS_INITIATED",\ "timestamp": "1749197480",\ "status": "COMPLETED",\ "start_time": "1749196900",\ "end_time": "1749197480",\ "duration": 580,\ "biz_opaque_callback_data": "0fS5cePMok"\ }\ ],\ "errors": [\ { "code": 138019, "message": "Call setup failed" }\ ]\ }\ }\ ]\ }\ ] } | Field | Notes | | --- | --- | | `status` | `COMPLETED` (call was answered) or `FAILED` | | `start_time`, `end_time`, `duration` | Only present when the other party picked up. `duration` is in seconds. | | `errors` | Present only on failed calls — see [Troubleshooting → Calling Error Codes](https://dualhook.com/docs/calling-troubleshooting#calling-error-codes) | Limits[](https://dualhook.com/docs/calling-business-initiated#limits) ---------------------------------------------------------------------- | Limit | Default | | --- | --- | | New BIC calls per business phone number | 10,000 / 24h | | Connected BIC calls per business + user pair | 100 / 24h (sandbox: relaxed) | | Connected BIC calls per business + user pair (per pricing tier) | 5 / 24h on the lowest-volume tier | | Concurrent inbound calls per phone number | 1,000 | | Consecutive unanswered BIC calls before "reconsider" system message | 2 (sandbox: 5) | | Consecutive unanswered BIC calls before permission auto-revoked | 4 (sandbox: 10) | A connected call **resets** unanswered counters and call-permission-request rate limits between that business and that user. Common Errors[](https://dualhook.com/docs/calling-business-initiated#common-errors) ------------------------------------------------------------------------------------ | Code | Meaning | Fix | | --- | --- | --- | | `138000` | Calling not enabled | Configure call settings (see [Calling Configuration](https://dualhook.com/docs/calling-configuration)
) | | `138001` | Receiver uncallable (not on WhatsApp, hasn't accepted ToS, or unsupported client) | Confirm with recipient | | `138002` | Concurrent calls limit (1,000) reached | Throttle inbound capacity | | `138003` | Duplicate call — one is already ongoing with the receiver | Wait for current call to end | | `138005` | Call rate limit exceeded | Reduce sending rate | | `138006` | No approved call permission found | Send permission request first ([Call Permissions](https://dualhook.com/docs/calling-permissions)
) | | `138012` | BIC daily limit hit (100 connected calls / 24h) | Wait for next-call timestamp in `error_data` | | `138013` | BIC not available for this business phone number country | Check [availability list](https://dualhook.com/docs/calling#availability) | | `138014` | Calling temporarily disabled (low quality) | Improve outreach quality, wait for restriction expiration | | `138017` | Permission request can't be sent — permanent permission already exists | Just place the call directly | Full error reference and SIP-specific errors are on [Calling Troubleshooting](https://dualhook.com/docs/calling-troubleshooting) . Related ------- * [Cloud API Calling](https://dualhook.com/docs/calling) Voice calling on WhatsApp Business Cloud API: VoIP via WebRTC or SIP, business-initiated and user-initiated calls, prerequisites, availability, and integration patterns. * [Call Permissions](https://dualhook.com/docs/calling-permissions) Obtain permission to call WhatsApp users: temporary (7-day) and permanent permissions, three ways to obtain (free-form request, template request, callback auto-prompt, business profile), get permission state endpoint, permission reply webhook. * [Call Buttons & Deep Links](https://dualhook.com/docs/calling-call-buttons) Drive WhatsApp calls from messages and links: voice\_call interactive messages, voice\_call template buttons, wa.me/call/ deep links with biz\_payload tracking. * [Calling Pricing](https://dualhook.com/docs/calling-pricing) How Meta bills WhatsApp Calling: free user-initiated calls, per-pulse business-initiated billing, volume tiers, customer service window interaction, call permission request charges, billing prerequisites. * [Calling Troubleshooting](https://dualhook.com/docs/calling-troubleshooting) WhatsApp Calling errors and fixes: full error code table (138000-138023), SIP-specific status codes, media-flow timeouts, audio clipping causes and mitigations (SDES, pre-accept), quick triage guide. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp BSUID API Changes: recipient, to precedence, groups and calling | Dualhook Browse Docs BSUID API and Sending Changes ============================= How WhatsApp APIs use BSUIDs and parent BSUIDs in the recipient field for messages, Marketing Messages API, groups, block users, calling, and SIP. This page covers API request and response changes for BSUID support. For webhook payload changes, see [BSUID webhook changes](https://dualhook.com/docs/contacts-bsuid-webhooks) . Messages API[](https://dualhook.com/docs/contacts-bsuid-api-changes#messages-api) ---------------------------------------------------------------------------------- Messages API requests add `recipient`, which accepts a BSUID or parent BSUID. { "messaging_product": "whatsapp", "recipient_type": "individual", "recipient": "", "type": "text", "text": { "body": "Hello" } } Rules: * Use `to` to send to a phone number. * Use `recipient` to send to a BSUID or parent BSUID. * Dualhook's `/messages` runtime accepts `recipient` without `to`. * If both `to` and `recipient` are present, `to` takes precedence. * Meta recommends sending to phone numbers when possible so phone-number fields can continue appearing in webhooks. Response changes: | Field | Behavior | | --- | --- | | `contacts[].input` | Returns the phone number, BSUID, parent BSUID, or group ID used in the request. | | `contacts[].wa_id` | Returned when the send used a phone number. Omitted for BSUID-only sends. | | `contacts[].user_id` | Returned when the send used a BSUID or parent BSUID. Omitted for phone-number-only sends. | If you send both `to` and `recipient`, the response follows the phone-number path because `to` takes precedence. Marketing Messages API[](https://dualhook.com/docs/contacts-bsuid-api-changes#marketing-messages-api) ------------------------------------------------------------------------------------------------------ The Marketing Messages API for WhatsApp also supports `to` and `recipient`. { "messaging_product": "whatsapp", "recipient_type": "individual", "to": "", "recipient": "", "type": "template", "template": { "name": "", "language": { "code": "en_US" } } } The same precedence applies: `to` wins over `recipient`. Response fields mirror the Messages API response, with `messages[].message_status` included for Marketing Messages API pacing. Unsupported Message Types[](https://dualhook.com/docs/contacts-bsuid-api-changes#unsupported-message-types) ------------------------------------------------------------------------------------------------------------ Meta adds error code `131062` for unsupported BSUID recipients: | Code | Meaning | | --- | --- | | `131062` | Business-scoped User ID recipients are not supported for this message. | One-tap, zero-tap, and copy-code authentication templates still require phone numbers. Do not send those templates to BSUIDs. Groups API[](https://dualhook.com/docs/contacts-bsuid-api-changes#groups-api) ------------------------------------------------------------------------------ Group API responses add BSUID fields while making phone numbers conditional. `GET` group info and join-request responses can include: * `wa_id` when phone number can be included, * `user_id`, * `parent_user_id` when enabled, * `username` when the user has adopted a username. Remove participant requests can use either `user` or `user_id`, but not both: { "messaging_product": "whatsapp", "participants": [\ {\ "user_id": "US.13491208655302741918"\ }\ ] } Block Users API[](https://dualhook.com/docs/contacts-bsuid-api-changes#block-users-api) ---------------------------------------------------------------------------------------- Block and unblock requests support phone numbers or regular BSUIDs. { "messaging_product": "whatsapp", "block_users": [\ {\ "user_id": "US.13491208655302741918"\ }\ ] } Rules: * Use `user` for phone numbers. * Use `user_id` for regular BSUIDs. * If both are provided, `user` takes precedence. * Parent BSUIDs are not supported for block or unblock requests. Responses can include `input`, `wa_id`, and `user_id`. `wa_id` is omitted when the request used a BSUID and Meta cannot include the phone number. `GET` blocked users can return `wa_id`, `user_id`, and `parent_user_id` when enabled and available. Calling API[](https://dualhook.com/docs/contacts-bsuid-api-changes#calling-api) -------------------------------------------------------------------------------- Business-initiated call requests add `recipient`: { "messaging_product": "whatsapp", "to": "", "recipient": "", "action": "connect", "session": { "sdp_type": "offer", "sdp": "" } } Rules match messaging: * `to` calls a phone number. * `recipient` calls a BSUID or parent BSUID. * If both are present, `to` takes precedence. Call permission lookup can use `user_wa_id` for phone numbers or `recipient` for BSUIDs and parent BSUIDs. Calling webhooks add user identifier fields such as `from_user_id`, `to_user_id`, `recipient_user_id`, and parent equivalents. See [BSUID webhook changes](https://dualhook.com/docs/contacts-bsuid-webhooks#groups-and-calling-webhooks) . SIP Signaling[](https://dualhook.com/docs/contacts-bsuid-api-changes#sip-signaling) ------------------------------------------------------------------------------------ SIP signaling can carry BSUIDs in places that previously carried phone numbers: * SIP request URI or `To` / `From` values can include the BSUID or parent BSUID when the call used that identifier. * User-initiated SIP messages can include `x-wa-meta-user-id`, `x-wa-meta-parent-user-id`, and `x-wa-meta-username`. If you terminate SIP yourself, update SIP identity parsing and logging so BSUID-formatted identifiers do not fail phone-number validation. Dualhook Boundary[](https://dualhook.com/docs/contacts-bsuid-api-changes#dualhook-boundary) -------------------------------------------------------------------------------------------- Dualhook's runtime supports the allowlisted `/messages` route and relays the payload without modification or persistence. It does not expose `/marketing_messages`, Groups API, Block Users API, Calling API, or SIP operations. Those additional APIs require a separate customer-owned Meta app and authorization. Your backend must choose when to use `to`, when to use `recipient`, and how to store returned identifiers. Inbound events still arrive directly from Meta through Webhook Override. Related ------- * [Meta BSUID, Usernames & Contact Book Transition Guide](https://dualhook.com/docs/contacts-bsuid) Overview of Meta's WhatsApp BSUID rollout: usernames, contact book behavior, parent BSUIDs, API recipient support, webhook changes, and migration timeline. * [BSUID Webhook Changes](https://dualhook.com/docs/contacts-bsuid-webhooks) WhatsApp webhook payload changes for BSUIDs, usernames, parent BSUIDs, optional phone-number fields, statuses, contacts, groups, calling, and Coexistence events. * [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) Reference for Meta's separate /marketing\_messages product, which is not exposed by Dualhook's runtime or Embedded Signup configuration. * [Messaging Overview](https://dualhook.com/docs/messaging-overview) Core messaging endpoint, text messages, formatting, delivery statuses, and common errors. * [Calling API Reference](https://dualhook.com/docs/calling-api-reference) Consolidated WhatsApp Calling reference: every endpoint, every webhook, WebRTC and ICE guidelines, DTLS certificate recommendations, SDP requirements, bandwidth budget, IP allowlisting. * [Meta API Error Reference](https://dualhook.com/docs/api-errors) Complete WhatsApp Cloud API and Meta Graph error code reference — what each code means, where it appears, and how to fix it. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Interactive Messages: CTA, List, Carousel, Buttons, Location | Dualhook Browse Docs Interactive Service Messages ============================ Interactive WhatsApp messages: CTA URL button, reply buttons, list, media carousel, and location request. Includes payload shapes and inbound-webhook formats for each type. What Interactive Service Messages Are[](https://dualhook.com/docs/service-messages-interactive#what-interactive-service-messages-are) -------------------------------------------------------------------------------------------------------------------------------------- Interactive service messages are non-template messages that include UI elements — buttons, lists, carousels, location-request prompts — beyond plain text or media. They are sent inside an open [customer service window](https://dualhook.com/docs/service-messages#the-customer-service-window) using the standard `/messages` endpoint with `type: "interactive"`. User responses (button taps, list selections, location shares) come back as inbound `messages` webhooks with the relevant action payload. In Dualhook setups, these webhooks go directly to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) — Dualhook does not ingest them. The Interactive Wrapper[](https://dualhook.com/docs/service-messages-interactive#the-interactive-wrapper) ---------------------------------------------------------------------------------------------------------- All interactive types share the same outer structure: { "messaging_product": "whatsapp", "recipient_type": "individual", "to": "12015550123", "type": "interactive", "interactive": { "type": "", "header": { /* optional, type-specific shape */ }, "body": { "text": "" }, "footer": { "text": "" }, "action": { /* required, type-specific shape */ } } } `interactive.type` selects which sub-type you're sending. The `header`, `body`, `footer`, and `action` fields each have type-specific shapes. Body is required for every type; header and footer are usually optional. Type Overview[](https://dualhook.com/docs/service-messages-interactive#type-overview) -------------------------------------------------------------------------------------- | `interactive.type` | What it does | Header? | Footer? | | --- | --- | --- | --- | | `cta_url` | Map a URL to a button | Optional (text/image/video/document) | Optional | | `button` | Up to 3 reply buttons | Optional (text/image/video/document) | Optional | | `list` | Modal list with up to 10 sections × 10 rows | Optional (text only) | Optional | | `carousel` | Horizontal scroll of 2–10 media cards | Not supported | Not supported | | `location_request_message` | Send-location button | Not supported | Not supported | | `address_message` | India-only address form (see [Address Messages](https://dualhook.com/docs/service-messages-address)
) | — | — | | `flow` | WhatsApp Flows (see Meta's Flows docs) | — | — | CTA URL Button (cta\_url)[](https://dualhook.com/docs/service-messages-interactive#cta-url-button-cta_url) ----------------------------------------------------------------------------------------------------------- Map a URL to a labeled button instead of dumping a raw URL into message text. Useful when the URL is long or obfuscated by tracking parameters. { "messaging_product": "whatsapp", "to": "12015550123", "type": "interactive", "interactive": { "type": "cta_url", "header": { "type": "image", "image": { "link": "https://example.com/banner.png" } }, "body": { "text": "Tap the button below to see available dates." }, "footer": { "text": "Dates subject to change." }, "action": { "name": "cta_url", "parameters": { "display_text": "See Dates", "url": "https://example.com?clickID=abc123" } } } } | Field | Limit | Notes | | --- | --- | --- | | `body.text` | 1024 chars | Required. URLs auto-hyperlinked. | | `header.type` | — | `text`, `image`, `video`, or `document`. Optional. | | `footer.text` | 60 chars | Optional. URLs auto-hyperlinked. | | `action.parameters.display_text` | 20 chars | Required. Button label. | | `action.parameters.url` | — | Required. Loaded in default browser on tap. | Reply Buttons (button)[](https://dualhook.com/docs/service-messages-interactive#reply-buttons-button) ------------------------------------------------------------------------------------------------------ Up to **3** predefined reply buttons. Each tap triggers an inbound `messages` webhook with the button's `id` so your backend can route the user's choice. { "messaging_product": "whatsapp", "to": "12015550123", "type": "interactive", "interactive": { "type": "button", "header": { "type": "text", "text": "Choose an option" }, "body": { "text": "How can we help today?" }, "footer": { "text": "Reply with one option." }, "action": { "buttons": [\ { "type": "reply", "reply": { "id": "track_order", "title": "Track order" } },\ { "type": "reply", "reply": { "id": "talk_to_agent","title": "Talk to agent" } },\ { "type": "reply", "reply": { "id": "stop_messages","title": "Stop messages" } }\ ] } } } | Field | Limit | | --- | --- | | `action.buttons` | Max 3 | | `reply.id` | 256 chars | | `reply.title` | 20 chars | List (list)[](https://dualhook.com/docs/service-messages-interactive#list-list) -------------------------------------------------------------------------------- A list message displays a single button in the chat that, when tapped, opens a modal with up to 10 sections, each containing up to 10 rows. Total row count across all sections is capped at 10. { "messaging_product": "whatsapp", "to": "12015550123", "type": "interactive", "interactive": { "type": "list", "header": { "type": "text", "text": "Choose a category" }, "body": { "text": "Pick one and we'll send relevant updates." }, "footer": { "text": "You can change preferences anytime." }, "action": { "button": "Choose category", "sections": [\ {\ "title": "Notifications",\ "rows": [\ { "id": "orders", "title": "Order updates", "description": "Shipping, delivery, returns" },\ { "id": "billing", "title": "Billing alerts", "description": "Invoices and renewals" }\ ]\ },\ {\ "title": "Promotions",\ "rows": [\ { "id": "deals", "title": "Deals", "description": "Weekly offers" }\ ]\ }\ ] } } } | Field | Limit | | --- | --- | | `action.button` | 20 chars (label on the modal-trigger button) | | `sections` | Max 10 | | `rows` (per section) | Max 10 | | Total `rows` across all sections | Max 10 | | `row.title` | 24 chars | | `row.description` | 72 chars | Media Carousel (carousel)[](https://dualhook.com/docs/service-messages-interactive#media-carousel-carousel) ------------------------------------------------------------------------------------------------------------ Horizontally scrollable cards (2–10), each with an image or video header, optional body text, and either **one URL button** or **one or more quick-reply buttons**. Button shape and count must be identical across all cards. { "messaging_product": "whatsapp", "to": "12015550123", "type": "interactive", "interactive": { "type": "carousel", "body": { "text": "Here are three of our latest arrivals:" }, "action": { "cards": [\ {\ "card_index": 0,\ "type": "cta_url",\ "header": { "type": "image", "image": { "link": "https://example.com/blue.jpg" } },\ "body": { "text": "*Blue Echeveria*\n\nA rosette-shaped succulent." },\ "action": {\ "name": "cta_url",\ "parameters": { "display_text": "Buy now", "url": "https://shop.example.com/blue" }\ }\ },\ {\ "card_index": 1,\ "type": "cta_url",\ "header": { "type": "image", "image": { "link": "https://example.com/zebra.jpg" } },\ "body": { "text": "*Zebra Haworthia*\n\nWhite stripes on green leaves." },\ "action": {\ "name": "cta_url",\ "parameters": { "display_text": "Buy now", "url": "https://shop.example.com/zebra" }\ }\ }\ ] } } } For quick-reply variants, use `action.buttons` (array of `quick_reply` objects with `id` + `title`) on each card instead of `action.parameters`. | Field | Limit | | --- | --- | | `interactive.body.text` | 1024 chars (required) | | `cards` | Min 2, max 10 | | `card.body.text` | 160 chars, up to 2 line breaks (optional) | | `card.header.type` | `image` or `video` only | | URL button `display_text` | 20 chars | | Quick-reply `title` | 20 chars | | Quick-reply `id` | 256 chars | Headers, footers, and other interactive components are **not** supported on the carousel wrapper itself — only on individual cards. All cards must use the same button shape. Location Request (location\_request\_message)[](https://dualhook.com/docs/service-messages-interactive#location-request-location_request_message) -------------------------------------------------------------------------------------------------------------------------------------------------- Display body text plus a "send location" button. When the user taps it, WhatsApp opens the location-sharing screen. { "messaging_product": "whatsapp", "to": "12015550123", "type": "interactive", "interactive": { "type": "location_request_message", "body": { "text": "Let's start with your pickup. *Enter an address* or *share your current location*." }, "action": { "name": "send_location" } } } When the user shares their location, you receive a `messages` webhook with `type: "location"`: { "messages": [\ {\ "context": {\ "from": "15550001234",\ "id": "wamid.HBgL..."\ },\ "from": "12015550123",\ "id": "wamid.HBgL...",\ "timestamp": "1702920965",\ "location": {\ "latitude": 40.782910059774,\ "longitude": -73.959075808525,\ "name": "Solomon R. Guggenheim Museum",\ "address": "1071 5th Ave, New York, NY 10128"\ },\ "type": "location"\ }\ ] } `name` and `address` only appear if the user chose to share them. `latitude`/`longitude` are always present when the user shares. Responses and Webhooks[](https://dualhook.com/docs/service-messages-interactive#responses-and-webhooks) -------------------------------------------------------------------------------------------------------- The `/messages` API response shape is the same as for any service message: a `messages` array with the WhatsApp message ID. Acceptance does not confirm delivery — see [Messaging Overview → Delivery Status Lifecycle](https://dualhook.com/docs/messaging-overview#delivery-status-lifecycle) . User actions on interactive messages (button taps, list selections, location shares) arrive as inbound `messages` webhooks. Common shapes: | User action | Inbound webhook `type` | Key fields | | --- | --- | --- | | Tap a `button` reply | `interactive.button_reply` | `interactive.button_reply.id`, `interactive.button_reply.title` | | Pick a `list` row | `interactive.list_reply` | `interactive.list_reply.id`, `interactive.list_reply.title`, `interactive.list_reply.description` | | Tap a carousel quick-reply | `interactive.button_reply` | as above (per card) | | Tap a CTA URL button | No inbound webhook for the tap itself (the URL opens externally) | — | | Share location | `location` | `latitude`, `longitude`, optional `name`/`address` | For Dualhook customers these webhooks bypass our app entirely — they go straight to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) . See [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) for full envelope and signature details. Related ------- * [Service Messages](https://dualhook.com/docs/service-messages) Free-form messages inside an open customer service window: types, replies, TTL, and October 2026 pricing with 1,000 free messages per number each month. * [Address Messages (India)](https://dualhook.com/docs/service-messages-address) India-only interactive form for collecting structured shipping addresses inside WhatsApp. Covers prefill, saved addresses, validation errors, the nfm\_reply submission webhook, and feature-not-supported (error 1026) fallback. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 132000: Template Variable Mismatch | Dualhook Browse Docs WhatsApp Error 132000: Template Variable Mismatch ================================================= WhatsApp error 132000 means the parameter count in your request doesn't match the approved template. Fix body, header, and button variables to match exactly. > **TL;DR:** WhatsApp error `132000` means the number of variable values you sent does not match the variable slots in the approved template. Fix the component structure and parameter count to match the approved template exactly — across body, header, and button variables. What Error 132000 Means[](https://dualhook.com/docs/whatsapp-error-132000#what-error-132000-means) --------------------------------------------------------------------------------------------------- Meta's description is that the number of variable parameter values in the request does not match the number defined in the template. The `details` string is component-specific, e.g. `body: number of localizable_params (0) does not match the expected number of params (1)`. Your send payload and the approved template schema are out of sync. Where You See It[](https://dualhook.com/docs/whatsapp-error-132000#where-you-see-it) ------------------------------------------------------------------------------------- Normally an **immediate API error** from `POST //messages` (or `/marketing_messages`). Like `132001`, it's usually **not** a failed-status webhook because Meta rejects the request before accepting it. Common Causes[](https://dualhook.com/docs/whatsapp-error-132000#common-causes) ------------------------------------------------------------------------------- * Fewer (or extra) body variables than the approved template expects. * A header or button variable forgotten. * The `components` array built in the wrong structure or order. * The template was edited/recreated but your app still sends the old shape. How to Fix It[](https://dualhook.com/docs/whatsapp-error-132000#how-to-fix-it) ------------------------------------------------------------------------------- 1. Open the approved template and inspect every variable-bearing component (header, body, buttons). 2. Count variables **per component**, not just overall. 3. Match your payload exactly: correct component type, parameter order, and count. 4. Remove empty placeholders, null filler, or "just in case" extras. 5. Re-test with a known-good payload; if you map CRM fields dynamically, log the rendered payload and compare it to the template definition. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-132000#how-to-prevent-it) --------------------------------------------------------------------------------------- * Generate payloads from stored template metadata, not hand-built JSON. * Add server-side validation before enqueueing. * Run payload contract tests whenever a template is approved, edited, or cloned. Related Errors[](https://dualhook.com/docs/whatsapp-error-132000#related-errors) --------------------------------------------------------------------------------- * [`/docs/whatsapp-error-132001`](https://dualhook.com/docs/whatsapp-error-132001) — template lookup failure * [`/docs/whatsapp-error-131000`](https://dualhook.com/docs/whatsapp-error-131000) — generic send failure * [`/docs/whatsapp-error-131047`](https://dualhook.com/docs/whatsapp-error-131047) — 24-hour window * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-132000#faq) ----------------------------------------------------------- **Why does WhatsApp say "number of parameters does not match"?** Your payload no longer structurally matches the approved template. **Do header and button variables count?** Yes — `132000` is not only about the body. **Can this happen after a template edit?** Yes — if the template changed but your payload builder didn't. **Is 132000 retryable?** Not by itself — fix the payload first. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-132000#how-dualhook-helps) ----------------------------------------------------------------------------------------- `132000` is a **sender-backend** error; the fix is in your payload-building code. Dualhook relays the request without storing or logging its body and returns Meta's error unchanged. Template metadata and [Messaging Health Status](https://dualhook.com/docs/health-monitoring) can help you spot a mismatched definition faster. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Shopify WhatsApp Integration with Dualhook | Dualhook Browse Docs Shopify WhatsApp Integration ============================ Connect Shopify order events to WhatsApp template messages via Dualhook and Cloud API. Prerequisites[](https://dualhook.com/docs/guide-shopify-integration#prerequisites) ----------------------------------------------------------------------------------- * A Shopify store with webhook access (Shopify admin, REST API, or GraphQL Admin API) * A WhatsApp Business Account connected through [Dualhook](https://dualhook.com/docs/getting-started) * [Webhook Override](https://dualhook.com/docs/webhook-override) configured for direct message routing * A server to receive both Shopify and WhatsApp webhooks (Node.js, Laravel, or any HTTP server) * Approved [WhatsApp message templates](https://dualhook.com/docs/templates) for your order notifications * Customer opt-in for WhatsApp notifications (required by [WhatsApp template policy](https://business.whatsapp.com/policy) ) Set these environment variables: SHOPIFY_WEBHOOK_SECRET=your_shopify_secret DUALHOOK_API_KEY=dh_live_your_key WHATSAPP_PHONE_NUMBER_ID=your_phone_number_id WHATSAPP_WABA_ID=your_waba_id > **No `META_APP_SECRET`?** In the Embedded Signup flow `X-Hub-Signature-256` is signed by Dualhook's Meta app and isn't customer-verifiable — validate inbound WhatsApp webhooks by payload shape instead. `SHOPIFY_WEBHOOK_SECRET` is unaffected. See [Messaging Webhook → Inbound Webhook POST Validation](https://dualhook.com/docs/messaging-webhook#signature-validation-post) > . Architecture Overview[](https://dualhook.com/docs/guide-shopify-integration#architecture-overview) --------------------------------------------------------------------------------------------------- This integration connects two webhook flows through your server: 1. **Shopify order events** — Shopify sends webhooks to your server when orders are created, fulfilled, or cancelled. 2. **WhatsApp template messages** — Your server sends template messages to customers via the Cloud API when order events occur. 3. **WhatsApp replies** — Meta delivers customer replies directly to your server via Webhook Override. Dualhook is not used as a message-storage layer. Shopify → Your Server → Dualhook Runtime → Meta Cloud API → Customer WhatsApp ↑ Customer WhatsApp → Meta → Your Server (via Webhook Override) Dualhook handles the initial WhatsApp configuration (Embedded Signup, Webhook Override, token management) and operational monitoring. Your server handles the business logic of connecting Shopify events to WhatsApp messages. Receive Shopify Order Webhooks[](https://dualhook.com/docs/guide-shopify-integration#receive-shopify-order-webhooks) --------------------------------------------------------------------------------------------------------------------- Register Shopify webhooks via the Shopify Admin REST API or GraphQL Admin API for the order events you want to trigger WhatsApp messages. Shopify requires your server to respond within 5 seconds or the delivery is considered failed. import express from "express"; import crypto from "crypto"; const app = express(); app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } })); const SHOPIFY_SECRET = process.env.SHOPIFY_WEBHOOK_SECRET; // Verify Shopify webhook signature (HMAC-SHA256, base64-encoded) function verifyShopify(req) { const hmac = req.headers["x-shopify-hmac-sha256"]; if (!hmac) return false; const hash = crypto .createHmac("sha256", SHOPIFY_SECRET) .update(req.rawBody) .digest("base64"); const hmacBuf = Buffer.from(hmac); const hashBuf = Buffer.from(hash); // Guard against length mismatch — timingSafeEqual throws if lengths differ if (hmacBuf.length !== hashBuf.length) return false; return crypto.timingSafeEqual(hmacBuf, hashBuf); } // Normalise phone to E.164 format (WhatsApp requires this) function toE164(phone) { if (!phone) return null; const digits = phone.replace(/\D/g, ""); if (!digits) return null; return digits.startsWith("+") ? digits : "+" + digits; } app.post("/shopify/orders/create", (req, res) => { if (!verifyShopify(req)) return res.sendStatus(401); res.sendStatus(200); const order = req.body; const phone = toE164(order.customer?.phone || order.shipping_address?.phone); if (phone) { sendOrderConfirmation(phone, order); } }); app.post("/shopify/orders/fulfilled", (req, res) => { if (!verifyShopify(req)) return res.sendStatus(401); res.sendStatus(200); const order = req.body; const phone = toE164(order.customer?.phone || order.shipping_address?.phone); if (phone) { sendFulfillmentNotification(phone, order); } }); Send WhatsApp Templates on Order Events[](https://dualhook.com/docs/guide-shopify-integration#send-whatsapp-templates-on-order-events) --------------------------------------------------------------------------------------------------------------------------------------- When a Shopify order event fires, send the corresponding WhatsApp template through Dualhook's allowlisted runtime. The `to` field must be in E.164 format (e.g. `+14155552671`). Only send template messages to customers who have opted in to receive WhatsApp notifications. const PHONE_NUMBER_ID = process.env.WHATSAPP_PHONE_NUMBER_ID; const DUALHOOK_API_KEY = process.env.DUALHOOK_API_KEY; async function sendOrderConfirmation(customerPhone, order) { const response = await fetch( `https://api.dualhook.com/v25.0/${PHONE_NUMBER_ID}/messages`, { method: "POST", headers: { Authorization: `Bearer ${DUALHOOK_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ messaging_product: "whatsapp", to: customerPhone, type: "template", template: { name: "order_confirmation", language: { code: "en" }, components: [\ {\ type: "body",\ parameters: [\ { type: "text", text: order.name }, // Order number\ { type: "text", text: order.total_price }, // Total\ { type: "text", text: order.currency }, // Currency\ ],\ },\ ], }, }), } ); if (!response.ok) { const error = await response.json(); console.error("WhatsApp send failed:", error); } } async function sendFulfillmentNotification(customerPhone, order) { const trackingUrl = order.fulfillments?.[0]?.tracking_urls?.[0] ?? order.fulfillments?.[0]?.tracking_url ?? "No tracking available"; await fetch( `https://api.dualhook.com/v25.0/${PHONE_NUMBER_ID}/messages`, { method: "POST", headers: { Authorization: `Bearer ${DUALHOOK_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ messaging_product: "whatsapp", to: customerPhone, type: "template", template: { name: "order_shipped", language: { code: "en" }, components: [\ {\ type: "body",\ parameters: [\ { type: "text", text: order.name },\ { type: "text", text: trackingUrl },\ ],\ },\ ], }, }), } ); } Receive WhatsApp Replies[](https://dualhook.com/docs/guide-shopify-integration#receive-whatsapp-replies) --------------------------------------------------------------------------------------------------------- When customers reply to your template messages, Meta delivers the reply directly to your server via Webhook Override. Handle these replies on the same server that receives Shopify webhooks. Meta expects a 200 response within ~10 seconds. Validate by envelope, WABA ID, and `phone_number_id`. const EXPECTED_WABA_ID = process.env.WHATSAPP_WABA_ID; const EXPECTED_PHONE_NUMBER_ID = process.env.WHATSAPP_PHONE_NUMBER_ID; function isExpectedWhatsAppPayload(body) { if (!body || body.object !== "whatsapp_business_account") return false; if (!Array.isArray(body.entry) || body.entry.length === 0) return false; for (const entry of body.entry) { if (entry.id !== EXPECTED_WABA_ID) return false; if (!Array.isArray(entry.changes) || entry.changes.length === 0) return false; for (const change of entry.changes) { if (change.field !== "messages") return false; const phoneNumberId = change.value?.metadata?.phone_number_id; if (phoneNumberId !== EXPECTED_PHONE_NUMBER_ID) return false; } } return true; } app.post("/whatsapp/webhook", (req, res) => { if (!isExpectedWhatsAppPayload(req.body)) return res.sendStatus(401); res.sendStatus(200); for (const entry of req.body.entry ?? []) { for (const change of entry.changes ?? []) { if (change.field !== "messages") continue; for (const message of change.value.messages ?? []) { console.log(`Customer reply from ${message.from}: ${message.text?.body}`); // Route to your support workflow or inbox } } } }); Template Examples[](https://dualhook.com/docs/guide-shopify-integration#template-examples) ------------------------------------------------------------------------------------------- Create these templates in Dualhook's [template management](https://dualhook.com/docs/templates) dashboard and wait for Meta approval before using them in your integration. **Order confirmation** (`order_confirmation`): Hi! Your order {{1}} for {{2}} {{3}} has been confirmed. We'll send you a tracking update when it ships. **Order shipped** (`order_shipped`): Good news! Your order {{1}} has shipped. Track your delivery here: {{2}} **Delivery reminder** (`delivery_reminder`): Hi! Your order {{1}} is out for delivery today. Someone should be available to receive the package. For templates with media headers (product images, PDF invoices), see the [Template Elements](https://dualhook.com/docs/template-elements) documentation. Test with Dualhook[](https://dualhook.com/docs/guide-shopify-integration#test-with-dualhook) --------------------------------------------------------------------------------------------- 1. Connect your WhatsApp number through [Embedded Signup](https://dualhook.com/docs/embedded-signup) . 2. Create and get approval for your order notification templates in the Dualhook dashboard. 3. Register Shopify webhooks pointing to your server via the Shopify admin, the REST API, or the GraphQL Admin API. 4. Place a test order in your Shopify store. Your server should receive the Shopify webhook and send the WhatsApp template. 5. Check Dualhook's [webhook activity logs](https://dualhook.com/docs/basics-overview) for management event monitoring. 6. Reply to the WhatsApp message from a test phone. The reply arrives directly at your server from Meta. Your order notifications and customer replies flow between Meta and your server. Dualhook manages the WhatsApp configuration and operational monitoring — this routing separation is a Dualhook-specific configuration, not a Meta platform default. Related ------- * [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) How to send template messages via Cloud API with variables, media headers, and URL buttons. * [Templates](https://dualhook.com/docs/templates) Template lifecycle, categories, and management through Dualhook and Meta Graph API. * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 10: Fix Permission Denied / Terms | Dualhook Browse Docs WhatsApp Error 10: Permission Denied ==================================== WhatsApp error 10 means permission is not granted or has been removed — missing scopes, unaccepted terms, or (for Flows) a non-allowlisted number. How to fix it. > **TL;DR:** WhatsApp error 10 is an authorization error meaning permission for the requested action is missing or was removed. Meta's `details` string is **"Permission is either not granted or has been removed."** Causes include missing token scopes, eligibility/terms not met, and — for WhatsApp Flows with Endpoint — a phone number that isn't allowlisted for the business public key. Fix it by granting the right permissions and meeting eligibility. What Error 10 Means[](https://dualhook.com/docs/whatsapp-error-10#what-error-10-means) --------------------------------------------------------------------------------------- 10 is an **authorization** error (HTTP 403). Meta's official `details` value is: **"Permission is either not granted or has been removed."** Meta's guidance adds two specifics: for **WhatsApp Flows with Endpoint**, the phone number used to set your business public key must be allowlisted; and you must meet the **eligibility requirements** for the API you're calling — if you're not eligible for the endpoint, you'll receive error 10. Where You See It[](https://dualhook.com/docs/whatsapp-error-10#where-you-see-it) --------------------------------------------------------------------------------- **Synchronous** Graph API response (403 Forbidden). It is an access/authorization condition, not a per-message status webhook. It surfaces on whichever endpoint requires a permission your app doesn't (or no longer) holds. Common Causes[](https://dualhook.com/docs/whatsapp-error-10#common-causes) --------------------------------------------------------------------------- * The access token is missing `whatsapp_business_management` and/or `whatsapp_business_messaging`. * A permission was granted previously but later **removed/revoked**. * The business has not accepted required legal terms, or isn't **eligible** for the endpoint being called. * For **Flows with Endpoint**: the phone number used to set the business public key is not allowlisted. How to Fix It[](https://dualhook.com/docs/whatsapp-error-10#how-to-fix-it) --------------------------------------------------------------------------- 1. Check the connection Health tab and confirm both WhatsApp permissions are present. 2. If either permission is missing, reauthorize the connection through Embedded Signup. Dualhook keeps the replacement BISU token encrypted and does not reveal it. 3. Check the **eligibility requirements** for the specific API; complete any onboarding/terms acceptance required. 4. For **Flows with Endpoint**, ensure the phone number used to set the business public key is **allowlisted**. 5. Retry the request. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-10#how-to-prevent-it) ----------------------------------------------------------------------------------- * Use Dualhook's server-held BISU authorization and monitor for 403s. * Re-audit asset permissions after portfolio or app changes (revocations are a common surprise). * Track which endpoints have eligibility gates and confirm onboarding before going live. Related Errors[](https://dualhook.com/docs/whatsapp-error-10#related-errors) ----------------------------------------------------------------------------- * [WhatsApp Error 200](https://dualhook.com/docs/whatsapp-error-200) — no token / 200-series permission errors. * [WhatsApp Error 190](https://dualhook.com/docs/whatsapp-error-190) — token expired/invalid. * [WhatsApp Webhook Override (Error 100)](https://dualhook.com/docs/whatsapp-error-100-webhook-override) * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-10#faq) ------------------------------------------------------- **Q: My connection worked yesterday and now I get error 10 — why?** A: A permission was likely removed/revoked, or an asset assignment changed. Check connection Health and reauthorize if a scope is missing. **Q: Is error 10 about legal terms?** A: It can be — not meeting eligibility/terms for an endpoint returns error 10. Confirm onboarding/terms acceptance. **Q: I get error 10 only on Flows — why?** A: The phone number used to set your business public key must be allowlisted for Flows with Endpoint. **Q: How is error 10 different from 200?** A: 200's base case is a missing token; error 10 is permission not granted/removed for the action. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-10#how-dualhook-helps) ------------------------------------------------------------------------------------- This is a permissions code where Dualhook helps substantially. Because Dualhook onboards WABAs through **Embedded Signup** and operates with **advanced** `whatsapp_business_management` and `whatsapp_business_messaging` access, it can surface authorization and connection health — making a revoked permission or an incomplete-eligibility state visible in monitoring instead of as a sudden 403. See [WhatsApp Business API Permissions](https://dualhook.com/docs/whatsapp-business-api-permissions) , [Maintain Account Health](https://dualhook.com/docs/account-health) , and [Embedded Signup](https://dualhook.com/docs/embedded-signup) . The actual grant/allowlist still happens in Meta's tools; Dualhook gives you early, accurate signal about which connection or scope is the problem. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 131047: Re-Engagement / 24h Window Fix | Dualhook Browse Docs WhatsApp Error 131047: Re-Engagement Message Required ===================================================== WhatsApp error 131047 means 24+ hours passed since the customer last replied, so the service window is closed. Why it happens and how to fix it with templates. > **TL;DR:** WhatsApp error `131047` means more than 24 hours have passed since the recipient last replied, so the free-form customer service window is closed. To reach them you must send an approved **template** message — or wait for the user to message you again. It is the modern replacement for the legacy `470` error. What Error 131047 Means[](https://dualhook.com/docs/whatsapp-error-131047#what-error-131047-means) --------------------------------------------------------------------------------------------------- Meta's `details` string is: **"More than 24 hours have passed since the recipient last replied to the sender number."** (Meta's current title for this code uses "Re-engagement message" wording.) WhatsApp only allows free-form messages within 24 hours of the customer's last inbound message; outside that window only approved templates are allowed. The older error `470` carried the same meaning and is effectively superseded by `131047`. Where You See It[](https://dualhook.com/docs/whatsapp-error-131047#where-you-see-it) ------------------------------------------------------------------------------------- Usually an **immediate API error** (HTTP 400) when you POST a free-form message outside the window. Some middleware surfaces it in their own logs after attempting the send; the root condition is the same closed window. Common Causes[](https://dualhook.com/docs/whatsapp-error-131047#common-causes) ------------------------------------------------------------------------------- * The 24-hour service window expired since the user's last message. * A late-arriving inbound made your app think the window was open when it had lapsed. * Automation attempted a free-form reply instead of a template. * The reply was sent from a different business number than the one the user messaged. How to Fix It[](https://dualhook.com/docs/whatsapp-error-131047#how-to-fix-it) ------------------------------------------------------------------------------- 1. **Stop retrying the same free-form message.** 2. Send an **approved template** to re-open contact, or wait for the user to message you again. 3. Confirm you are replying from the same business number that received the user's last message. 4. If you believe the window should be open, compare the inbound webhook timestamp against actual receipt time — latency can make the UI show an open window that has lapsed. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-131047#how-to-prevent-it) --------------------------------------------------------------------------------------- * Maintain a per-contact / per-number "window-open-until" timestamp from inbound `messages` webhooks. * Default to templates whenever window state is unknown. * Ensure inbound webhooks land reliably so your app sees the message that opened the window. Related Errors[](https://dualhook.com/docs/whatsapp-error-131047#related-errors) --------------------------------------------------------------------------------- * [`/docs/whatsapp-error-131049`](https://dualhook.com/docs/whatsapp-error-131049) — marketing cap * [`/docs/whatsapp-error-132001`](https://dualhook.com/docs/whatsapp-error-132001) — template not found when you try the fallback * [`/docs/whatsapp-error-131026`](https://dualhook.com/docs/whatsapp-error-131026) — undeliverable * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-131047#faq) ----------------------------------------------------------- **What is WhatsApp error 131047?** The 24-hour customer service window closed; you must use a template. **Is 470 the same as 131047?** Conceptually yes — `470` is the legacy code; `131047` is current. **Why do I get 131047 within 24 hours?** Often message latency, a wrong business number, or a missed inbound webhook event. **Can I fix it by retrying later?** No — the fix is "use an approved template" or "wait for a new user message," not "retry unchanged." How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-131047#how-dualhook-helps) ----------------------------------------------------------------------------------------- This is a **send-side** rule fixed in your backend. Dualhook does not store conversation state or message content. Where it helps is reliable, low-latency delivery of the inbound `messages` webhooks your backend uses to compute window state — accurate inbound timestamps are exactly what prevent false 131047s. See [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) and [Messaging Health Status](https://dualhook.com/docs/health-monitoring) . [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 131031: Account Locked Fix | Dualhook Browse Docs WhatsApp Error 131031: Account Has Been Locked ============================================== WhatsApp error 131031 means your WABA is restricted/disabled for a policy violation, or request data (e.g. two-step PIN) could not be verified. How to recover. > **TL;DR:** WhatsApp error `131031` means your WhatsApp Business Account has been restricted/disabled for a platform-policy violation, or Meta could not verify request data (e.g. an incorrect two-step PIN). Pause sends, use the Health Status API to learn why, fix the policy issue or PIN, and appeal via Policy Enforcement. What Error 131031 Means[](https://dualhook.com/docs/whatsapp-error-131031#what-error-131031-means) --------------------------------------------------------------------------------------------------- Meta's `details` string is verbatim: **"The WhatsApp Business Account associated with the app has been restricted or disabled for violating a platform policy, or we were unable to verify data included in the request against data set on the WhatsApp Business Account (e.g, the two-step pin included in the request is incorrect)."** (Integrity error — confirmed on the live table.) Meta explicitly recommends the **Health Status API** for additional insight into the lock reason. Where You See It[](https://dualhook.com/docs/whatsapp-error-131031#where-you-see-it) ------------------------------------------------------------------------------------- Two patterns: an **immediate API error**, and a **failed status webhook** after the send initially returned a `wamid`. The second pattern (accepted then later failed) is widely reported and especially confusing. Common Causes[](https://dualhook.com/docs/whatsapp-error-131031#common-causes) ------------------------------------------------------------------------------- * Policy violation (messaging or commerce policy); account under review or disabled. * Incorrect two-step verification PIN in the request. * A delivery-pipeline account state stricter than what one UI panel shows. How to Fix It[](https://dualhook.com/docs/whatsapp-error-131031#how-to-fix-it) ------------------------------------------------------------------------------- 1. **Pause outbound sends** from the affected number. 2. Call the **Health Status API** to identify the specific reason. 3. If it's a PIN issue, supply the correct two-step PIN (reset if needed). 4. If it's policy enforcement, review the Policy Enforcement doc and submit a review/appeal. 5. If API and dashboard disagree, log `fbtrace_id`, message ID, timestamp, and webhook payload, and open Meta support with concrete examples. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-131031#how-to-prevent-it) --------------------------------------------------------------------------------------- * Follow the WhatsApp Business Messaging Policy and maintain template quality. * Store the correct two-step PIN; avoid repeated wrong guesses. * Monitor account/health webhooks for early restriction signals; keep a playbook for `LIMITED`/`BLOCKED` states. See [Maintain Account Health](https://dualhook.com/docs/account-health) . Related Errors[](https://dualhook.com/docs/whatsapp-error-131031#related-errors) --------------------------------------------------------------------------------- * [`/docs/whatsapp-error-130497`](https://dualhook.com/docs/whatsapp-error-130497) — country restriction * [`/docs/whatsapp-error-131048`](https://dualhook.com/docs/whatsapp-error-131048) — spam-rate restriction * [`/docs/whatsapp-error-190`](https://dualhook.com/docs/whatsapp-error-190) — token expired * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-131031#faq) ----------------------------------------------------------- **What is WhatsApp error 131031?** Your WABA is locked for a policy violation or failed data verification. **How do I find out why?** Use Meta's Health Status API. **Why does the send API return 200 but the webhook show 131031?** Acceptance and final delivery/account-state checks are different steps. **Is a wrong two-step PIN really 131031?** Yes — Meta lists an incorrect two-step PIN as a 131031 trigger. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-131031#how-dualhook-helps) ----------------------------------------------------------------------------------------- Account lock is a high-value area for Dualhook. Because it holds **advanced `whatsapp_business_management` access** and subscribes to account-level webhooks (such as `account_update`, `account_alerts`, and `account_review_update`), it can surface restriction and health signals and alert you the moment a WABA's status changes — often before you discover it via a failed send. See [Maintain Account Health](https://dualhook.com/docs/account-health) and [Messaging Health Status](https://dualhook.com/docs/health-monitoring) . Resolving the underlying policy/PIN issue still happens in Meta's tooling. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 132012: Fix Template Format Mismatch | Dualhook Browse Docs WhatsApp Error 132012: Template Parameter Format Mismatch ========================================================= WhatsApp error 132012 means template variable values are formatted incorrectly (e.g. header expected IMAGE, received UNKNOWN). Causes, fixes, and prevention. > **TL;DR:** WhatsApp error 132012 means the variable parameter values in your send request do not match the format defined in the approved template. Meta's `details` string is **"Variable parameter values formatted incorrectly."** The most common real-world variant is a media header that expects an image but receives an unrecognized value (`header: Format mismatch, expected IMAGE, received UNKNOWN`). Fix it by aligning every component's parameter type and value with the approved template. What Error 132012 Means[](https://dualhook.com/docs/whatsapp-error-132012#what-error-132012-means) --------------------------------------------------------------------------------------------------- Error 132012 is a template send error. It fires when the structure or type of the parameters you pass at send time does not match the structure of the template as approved in WhatsApp Manager. Meta's official `details` value is: **"Variable parameter values formatted incorrectly."** In practice the API often returns a more specific runtime message inside `error_data.details`. A widely reported example (community-observed, not the canonical Meta string) — captured verbatim on Microsoft Q&A for Azure Communication Services — is: `(#132012) Parameter format does not match format in the created template. header: Format mismatch, expected IMAGE, received UNKNOWN`. This appears when a template was created with an image header but the send payload omits a properly typed image header object. Where You See It[](https://dualhook.com/docs/whatsapp-error-132012#where-you-see-it) ------------------------------------------------------------------------------------- 132012 is a **synchronous** error. It is returned immediately in the Graph API HTTP response (typically `400 Bad Request`) when you POST to the `/messages` endpoint. The message is rejected before it is accepted for delivery, so you will **not** see it in a `statuses[].errors[]` status webhook — there is no message ID to report a status against. If your request returned `200` and the message later failed, you are looking at a different error, not 132012. Common Causes[](https://dualhook.com/docs/whatsapp-error-132012#common-causes) ------------------------------------------------------------------------------- * The template has a **media header** (image, video, document) but the send payload sends no header component, or sends it with the wrong `type`, producing `expected IMAGE, received UNKNOWN`. * A media header is passed but the `link` URL is unreachable, not publicly accessible, or returns a non-media MIME type, so Meta cannot resolve it to the expected media type. * A parameter that should be a typed object (e.g. `{"type":"image","image":{"link":"..."}}`) is sent as a plain text string. * **Button parameters** (URL button suffix, copy-code/coupon button, flow button) are passed in the wrong shape or omitted. * Named vs. positional parameter mismatch: the template was created with named parameters but the payload uses positional `{{1}}`\-style values, or vice versa. * Currency, date-time, or other typed body parameters are sent as raw strings instead of the structured object Meta expects. How to Fix It[](https://dualhook.com/docs/whatsapp-error-132012#how-to-fix-it) ------------------------------------------------------------------------------- 1. Open the template in **WhatsApp Manager** and note every component: header type (text/image/video/document), body variables, and each button. 2. For a **media header**, send a properly typed header component, e.g. `{"type":"header","parameters":[{"type":"image","image":{"link":"https://example.com/image.jpg"}}]}`. Confirm the URL is publicly reachable and returns a supported MIME type. 3. Match each **body** parameter's `type` to what the template expects. Per Meta's Templates "Parameter formats" docs, templates default to **positional** format unless you defined named parameters; named parameters must be unique, lowercase, underscore-only strings wrapped in double curly braces, e.g. `{{first_name}}`. 4. Send **button** parameters in the exact shape required for the button type (URL suffix, coupon code, or flow action payload). 5. Test by sending the template from WhatsApp Manager / API to a test number; if it sends there but not from your code, the defect is in your payload construction. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-132012#how-to-prevent-it) --------------------------------------------------------------------------------------- * Build the send payload programmatically from the template definition you fetch via the Message Template API, rather than hand-coding parameter arrays per template. * Validate that every media `link` is public and returns the correct `Content-Type` before sending. * Keep a regression test that sends each production template to a sandbox number after any template edit, since editing a template can change its component structure. * Decide on named or positional parameters at template creation and keep your sender consistent with that choice. Related Errors[](https://dualhook.com/docs/whatsapp-error-132012#related-errors) --------------------------------------------------------------------------------- * [WhatsApp Error 132000](https://dualhook.com/docs/whatsapp-error-132000) — parameter **count** mismatch (too few/many values). * [WhatsApp Error 131008](https://dualhook.com/docs/whatsapp-error-131008) — a required parameter is missing entirely. * [WhatsApp Error 132001](https://dualhook.com/docs/whatsapp-error-132001) — template not found / not approved. * [WhatsApp Webhook Override (Error 100)](https://dualhook.com/docs/whatsapp-error-100-webhook-override) — routing/override context. * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-132012#faq) ----------------------------------------------------------- **Q: What does "format mismatch, expected IMAGE, received UNKNOWN" mean?** A: Your template has an image header, but your send payload did not include a correctly typed image header object (or the media URL could not be resolved). Add a `header` component with a `type: image` parameter and a reachable `link`. **Q: Is 132012 the same as 132000?** A: No. 132012 is a **format/type** mismatch; 132000 is a **count** mismatch (the number of values doesn't equal the number of variables in the template). **Q: Why does my template work in WhatsApp Manager but fail with 132012 from the API?** A: Manager builds a correct payload for you. From the API you must construct the component objects yourself; the mismatch is in your payload, not the template. **Q: Does 132012 ever arrive via webhook?** A: No — it is rejected synchronously in the API response. You will not get a status webhook for a message that was never accepted. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-132012#how-dualhook-helps) ----------------------------------------------------------------------------------------- Error 132012 originates in the payload built by **your own sending backend**. Dualhook relays that payload without storing or logging its body and cannot rewrite your parameter arrays. It surfaces approved template structure so you can confirm the expected header, body, and button shapes before sending. Keep content-free Graph error metadata such as `code`, `error_data.details`, and `fbtrace_id` in your own logs. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 368: Fix Temporary Policy Block | Dualhook Browse Docs WhatsApp Error 368: Temporarily Blocked for Policy Violations ============================================================= WhatsApp error 368 means your WhatsApp Business Account was restricted or disabled for a platform policy violation. How to diagnose, appeal, and prevent it. > **TL;DR:** WhatsApp error 368 is an integrity error meaning your WhatsApp Business Account has been restricted or disabled for breaking a platform policy. Meta's `details` string is **"The WhatsApp Business Account associated with the app has been restricted or disabled for violating a platform policy."** Fix it by reviewing the violation in Business Support Home, correcting the behavior, and (where appealable) requesting a review. What Error 368 Means[](https://dualhook.com/docs/whatsapp-error-368#what-error-368-means) ------------------------------------------------------------------------------------------ 368 is an **integrity** error (HTTP 403). It indicates enforcement action against the WABA for violating the WhatsApp Business Messaging Policy, Commerce Policy, or Business Terms of Service. Meta's official `details` value is: **"The WhatsApp Business Account associated with the app has been restricted or disabled for violating a platform policy."** Per Meta's Policy Enforcement doc, enforcement escalates with severity and repetition — from short messaging blocks to indefinite account locks and, for severe violations, permanent disablement. Where You See It[](https://dualhook.com/docs/whatsapp-error-368#where-you-see-it) ---------------------------------------------------------------------------------- 368 appears **synchronously** on send attempts (403 Forbidden) once enforcement is active, and the underlying enforcement event is also pushed via the `account_update` webhook (events such as `ACCOUNT_VIOLATION` / `ACCOUNT_RESTRICTION`) and shown in WhatsApp Manager and the Business Manager Notifications Center. So you can learn about it either reactively (failed sends) or proactively (account webhook). Common Causes[](https://dualhook.com/docs/whatsapp-error-368#common-causes) ---------------------------------------------------------------------------- * Sending spam or messaging users who have not opted in. * Template misclassification or repeatedly low template quality / high block-and-report rates. * High-risk or prohibited content categories (e.g. alcohol, tobacco, drugs, gambling, adult content) under the Commerce/Business policies. * Repeated prior warnings that went unaddressed, escalating to a block. How to Fix It[](https://dualhook.com/docs/whatsapp-error-368#how-to-fix-it) ---------------------------------------------------------------------------- 1. Log into **Meta Business Suite → Business Support Home / Account Quality** and open the Account Overview to see the specific violation and any restriction window. 2. Call the **Health Status API** for additional insight into which node is restricted and why. 3. **Correct the behavior** — audit opt-in flows, template content/classification, and recipient lists. 4. If the violation is appealable, click **Request Review** and submit supporting business details. Appeal timing varies and Meta does not publish a fixed SLA — monitor Business Support Home for Meta's decision. 5. If a time-based block is not appealable, wait out the restriction window; do not keep retrying, which can worsen standing. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-368#how-to-prevent-it) ------------------------------------------------------------------------------------ * Subscribe to the **`account_update`** webhook for real-time enforcement notifications so you can react within minutes. * Maintain strong opt-in hygiene and high template quality ratings. * Read and follow the WhatsApp Business Messaging Policy and Commerce Policy; avoid restricted verticals. Related Errors[](https://dualhook.com/docs/whatsapp-error-368#related-errors) ------------------------------------------------------------------------------ * [WhatsApp Error 131031](https://dualhook.com/docs/whatsapp-error-131031) — account locked (policy or data-verification). * [WhatsApp Error 130497](https://dualhook.com/docs/whatsapp-error-130497) — restricted from messaging users in a country. * [WhatsApp Error 131064](https://dualhook.com/docs/whatsapp-error-131064) — messaging limit due to template classification violations. * [WhatsApp Webhook Override (Error 100)](https://dualhook.com/docs/whatsapp-error-100-webhook-override) * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-368#faq) -------------------------------------------------------- **Q: How long does a 368 block last?** A: It varies by severity — short messaging restrictions are common, while severe cases lead to indefinite locks or permanent disablement. The exact window is shown in Business Support Home. **Q: Can I appeal a 368?** A: Often yes, via Request Review in Business Support Home. Appeal timing varies (Meta doesn't publish a fixed SLA), and some time-based blocks can't be appealed — you wait them out. **Q: How do I know exactly which policy I broke?** A: Business Support Home / Account Quality shows the specific violation; the Health Status API adds node-level detail. **Q: Will retrying sends help?** A: No. Retrying during enforcement won't deliver and can hurt your standing. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-368#how-dualhook-helps) -------------------------------------------------------------------------------------- This is an account-level integrity code, so Dualhook helps substantially. With its advanced `whatsapp_business_management` access and account-level webhook visibility, Dualhook surfaces account-status and authorization-health signals — so a restriction shows up in your monitoring views and alerts rather than only as failed sends. See [Maintain Account Health](https://dualhook.com/docs/account-health) and [Messaging Health Status](https://dualhook.com/docs/health-monitoring) . Subscribing to `account_update` (so enforcement events route to your endpoint) lets you respond in minutes. The actual remediation — fixing behavior and filing the appeal in Business Support Home — happens in Meta's tools; Dualhook makes sure you find out fast and know which WABA is affected. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 200: Fix Missing Token / Permission | Dualhook Browse Docs WhatsApp Error 200: No Access Token / Permission Issue ====================================================== WhatsApp error 200 means no access token was provided ('Provide valid app ID'); in practice it also surfaces as a missing-permission-to-send error. How to fix both. > **TL;DR:** WhatsApp error 200 means no access token was provided on the request. Meta's `details` string is **"No access token was provided. The API returns the message 'Provide valid app ID'. This error occurs on certain GET endpoints (such as whatsapp\_business\_profile) when no token is included. Other endpoints may return error code 190 or 104 instead."** In practice the 200 family also surfaces as a missing-permission error when your token can't act on the WABA. Fix it by including a valid token with the right permissions. What Error 200 Means[](https://dualhook.com/docs/whatsapp-error-200#what-error-200-means) ------------------------------------------------------------------------------------------ 200 is an **authorization** error. The base case is a request that omitted an access token entirely; Meta returns "Provide valid app ID." Meta's official `details` value is: **"No access token was provided. The API returns the message 'Provide valid app ID'. This error occurs on certain GET endpoints (such as whatsapp\_business\_profile) when no token is included. Other endpoints may return error code 190 or 104 instead."** Meta's table also documents the **200–299** range with `details` "Permission is either not granted or has been removed." So a 200-series error often means your token lacks a required business-asset permission rather than being missing entirely. A very common **runtime message** developers see in this family is: `(#200) You do not have the necessary permissions required to send messages on behalf of this WhatsApp Business Account.` (community-observed runtime string) — which points to a missing `whatsapp_business_messaging` permission on the token. Where You See It[](https://dualhook.com/docs/whatsapp-error-200#where-you-see-it) ---------------------------------------------------------------------------------- **Synchronous** Graph API response (403/permission class). The literal 200 / "Provide valid app ID" case shows on GET endpoints like `whatsapp_business_profile` when no token is sent; permission-range (200–299) errors and the "necessary permissions" runtime message appear on any endpoint whose required permission your token doesn't hold (e.g. sending a message). It is not a per-message webhook. Common Causes[](https://dualhook.com/docs/whatsapp-error-200#common-causes) ---------------------------------------------------------------------------- * The request was sent with **no access token** (the literal 200 / "Provide valid app ID" case). * The token lacks `whatsapp_business_management` and/or `whatsapp_business_messaging` permissions (the "necessary permissions to send messages" runtime case). * A business-asset permission was granted earlier but later **removed/revoked**, or the asset assignment changed in the Business Portfolio. * Wrong token type (e.g. a user token where a system-user token is needed). How to Fix It[](https://dualhook.com/docs/whatsapp-error-200#how-to-fix-it) ---------------------------------------------------------------------------- 1. Confirm your request to `api.dualhook.com` includes `Authorization: Bearer dh_live_...`. 2. Check connection Health to verify the underlying Meta authorization carries `whatsapp_business_management` and `whatsapp_business_messaging`. 3. If permissions are missing, reauthorize the connection through Embedded Signup. 4. For 200-series permission errors, confirm the customer still grants Dualhook access to the correct WABA and phone number. 5. Retry; distinguish from **190** (expired/invalid token) and **104** (different auth failure). How to Prevent It[](https://dualhook.com/docs/whatsapp-error-200#how-to-prevent-it) ------------------------------------------------------------------------------------ * Keep Meta authorization server-side and rotate the connection's `dh_live_...` key deliberately. * Monitor for 401/403 auth failures and alert when they spike. * Audit asset permissions after any change to portfolio membership or app configuration. Related Errors[](https://dualhook.com/docs/whatsapp-error-200#related-errors) ------------------------------------------------------------------------------ * [WhatsApp Error 190](https://dualhook.com/docs/whatsapp-error-190) — access token expired/invalid. * [WhatsApp Error 10](https://dualhook.com/docs/whatsapp-error-10) — permission not granted / removed. * [WhatsApp Webhook Override (Error 100)](https://dualhook.com/docs/whatsapp-error-100-webhook-override) * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-200#faq) -------------------------------------------------------- **Q: I get "Provide valid app ID" — what's wrong?** A: Your request had no valid runtime credential. Add the connection's `dh_live_...` bearer key; this is distinct from an expired underlying Meta authorization. **Q: I get "(#200) You do not have the necessary permissions required to send messages on behalf of this WhatsApp Business Account." Why?** A: The underlying connection is missing `whatsapp_business_messaging` or the permission was revoked. Reauthorize the connection through Embedded Signup. **Q: Which permissions do I need?** A: `whatsapp_business_management` and `whatsapp_business_messaging`. **Q: How is 200 different from 190?** A: 200 = no token provided (or missing permission); 190 = token provided but expired/invalid. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-200#how-dualhook-helps) -------------------------------------------------------------------------------------- This is a permissions code where Dualhook adds substantial value. Dualhook connects your customer's WABA through Meta **Embedded Signup**, keeps the BISU token encrypted, and surfaces authorization health before runtime calls start failing with 200-series errors. Your backend only presents its connection-bound `dh_live_...` key to the allowlisted runtime. See [Runtime API Authentication](https://dualhook.com/docs/runtime-api) , [WhatsApp Business API Permissions](https://dualhook.com/docs/whatsapp-business-api-permissions) , [Maintain Account Health](https://dualhook.com/docs/account-health) , and [Embedded Signup](https://dualhook.com/docs/embedded-signup) . [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Timezone IDs | Dualhook Browse Docs Timezone IDs ============ Reference for the numeric timezone\_id field returned by Meta APIs. Maps to TZ\_\_ constants. Used by call\_hours.timezone\_id (which takes IANA name on write) and other Meta resources. What Timezone IDs Are[](https://dualhook.com/docs/calling-timezone-ids#what-timezone-ids-are) ---------------------------------------------------------------------------------------------- `timezone_id` is a **numeric** identifier returned on WhatsApp Business Account (WABA) objects and other Meta API resources. It maps to a named timezone constant (e.g. ID `7` = `TZ_AMERICA_NEW_YORK`). There are 591 timezones in the table. Meta uses the numeric ID in API responses but accepts IANA timezone names (e.g. `America/New_York`) on input fields like [`call_hours.timezone_id`](https://dualhook.com/docs/calling-configuration#call_hours) . Where They Appear[](https://dualhook.com/docs/calling-timezone-ids#where-they-appear) -------------------------------------------------------------------------------------- | Field | Type | Notes | | --- | --- | --- | | WABA `timezone_id` | Numeric (read) | Returned on `GET /?fields=timezone_id` | | `call_hours.timezone_id` | String IANA name (write) | e.g. `"America/Manaus"` — not the numeric ID | | Other Meta API resources | Numeric or string depending on endpoint | Always check the field's documented type | The asymmetry — numeric on read, IANA string on write for `call_hours` — is intentional. Use the named constants on the right side of the lookup table when you need to **set** a timezone, and use the numeric ID returned by the API when you need to **interpret** one. Lookup Table[](https://dualhook.com/docs/calling-timezone-ids#lookup-table) ---------------------------------------------------------------------------- The full list has 591 entries — too long to mirror here. Reference Meta's official table: [https://developers.facebook.com/docs/whatsapp/cloud-api/reference/timezones](https://developers.facebook.com/docs/whatsapp/cloud-api/reference/timezones) The constant naming convention is consistent: `TZ__`, all caps, slashes in the IANA name replaced with underscores. So: | IANA name | Constant | ID | | --- | --- | --- | | `America/New_York` | `TZ_AMERICA_NEW_YORK` | 7 | | `Europe/Vienna` | `TZ_EUROPE_VIENNA` | 12 | | `Asia/Dubai` | `TZ_ASIA_DUBAI` | 8 | Common Timezones[](https://dualhook.com/docs/calling-timezone-ids#common-timezones) ------------------------------------------------------------------------------------ | ID | Constant | IANA name | | --- | --- | --- | | 0 | `TZ_UNKNOWN` | — | | 1 | `TZ_AMERICA_LOS_ANGELES` | America/Los\_Angeles | | 2 | `TZ_AMERICA_DENVER` | America/Denver | | 3 | `TZ_PACIFIC_HONOLULU` | Pacific/Honolulu | | 4 | `TZ_AMERICA_ANCHORAGE` | America/Anchorage | | 5 | `TZ_AMERICA_PHOENIX` | America/Phoenix | | 6 | `TZ_AMERICA_CHICAGO` | America/Chicago | | 7 | `TZ_AMERICA_NEW_YORK` | America/New\_York | | 8 | `TZ_ASIA_DUBAI` | Asia/Dubai | | 12 | `TZ_EUROPE_VIENNA` | Europe/Vienna | | 588 | `TZ_UNIVERSAL` | UTC | | 589 | `TZ_W_SU` | W-SU (Moscow time, legacy alias) | | 590 | `TZ_ZULU` | Zulu (UTC) | | 591 | `TZ_AMERICA_COYHAIQUE` | America/Coyhaique | | 592 | `TZ_NUM_TIMEZONES` | (sentinel — not a real zone) | For the full mapping consult Meta's reference page above. The total table runs `0` to `592`. Look Up via API[](https://dualhook.com/docs/calling-timezone-ids#look-up-via-api) ---------------------------------------------------------------------------------- To check what timezone a WABA is in: GET /?fields=timezone_id { "id": "102290129340398", "timezone_id": 7 } To set the timezone on `call_hours` (uses the IANA name, **not** the numeric ID): POST //settings { "calling": { "call_hours": { "status": "ENABLED", "timezone_id": "America/New_York", "weekly_operating_hours": [/* ... */] } } } If you store both the numeric ID and the IANA name in your code, keep them in a single mapping table to avoid drift. Meta updates the table over time (new IDs append at the end — `TZ_AMERICA_COYHAIQUE` at 591 was added recently for example). Related ------- * [Calling Configuration](https://dualhook.com/docs/calling-configuration) Enable calling on a business phone number, configure call hours and holidays, restrict the call icon by country, set callback permissions, choose audio codecs, listen for settings webhooks. * [Cloud API Calling](https://dualhook.com/docs/calling) Voice calling on WhatsApp Business Cloud API: VoIP via WebRTC or SIP, business-initiated and user-initiated calls, prerequisites, availability, and integration patterns. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Maintain Account Health | Dualhook Browse Docs Maintain Account Health ======================= Ownership hygiene, quality monitoring, opt-out governance, and incident playbook. Why This Matters[](https://dualhook.com/docs/account-health#why-this-matters) ------------------------------------------------------------------------------ Strong account health protects your sending capacity and delivery reliability. Weak health increases risk of: * User blocks and reports * [Template](https://dualhook.com/docs/templates) rejections * Reduced [messaging limits](https://dualhook.com/docs/capacity-quality-limits) * Temporary or permanent account restrictions Core Health Pillars[](https://dualhook.com/docs/account-health#core-health-pillars) ------------------------------------------------------------------------------------ 1. Ownership and access hygiene 2. [Phone-number](https://dualhook.com/docs/phone-numbers) quality hygiene 3. Template quality hygiene 4. Opt-out governance 5. Continuous monitoring and fast remediation Ownership and Access Hygiene[](https://dualhook.com/docs/account-health#ownership-and-access-hygiene) ------------------------------------------------------------------------------------------------------ Recommended controls: * Ensure the [WABA](https://dualhook.com/docs/whatsapp-business-accounts) is owned by the legal business operating the messaging. * Limit [Meta Business Portfolio](https://dualhook.com/docs/meta-business-portfolio) access to relevant team members only. * Remove stale users and unused system-user permissions. * Enforce least-privilege access for operational roles. Misaligned ownership or broad access can create compliance and security risk and lead to enforcement actions. Phone-Number Quality[](https://dualhook.com/docs/account-health#phone-number-quality) -------------------------------------------------------------------------------------- Number-level quality directly influences messaging limits and upgrade eligibility. Operational rules: * Keep [display names](https://dualhook.com/docs/display-names) aligned with the sending brand. * Monitor `quality_rating` and [health states](https://dualhook.com/docs/health-monitoring) daily. * Reduce campaign volume immediately when quality drops. * Avoid sudden volume spikes on newly active numbers. Template Quality and Approval[](https://dualhook.com/docs/account-health#template-quality-and-approval) -------------------------------------------------------------------------------------------------------- Templates should be expected, timely, and relevant for opted-in recipients. Common rejection and quality issues: * Vague or low-context content * Wrong template category * Poor parameter formatting * Language mismatch * Overly promotional tone without clear value ### Template Authoring Checklist[](https://dualhook.com/docs/account-health#template-authoring-checklist) * Use descriptive template names (business meaning, not generic labels). * Keep syntax clean (`{{1}}`, `{{2}}`, etc.). * Choose the right category (`MARKETING`, `UTILITY`, `AUTHENTICATION`). * Keep language and audience matching precise. * Use A/B variants to improve quality over time. For template structure details, see [Template Elements](https://dualhook.com/docs/template-elements) . Opt-Out Governance[](https://dualhook.com/docs/account-health#opt-out-governance) ---------------------------------------------------------------------------------- Opt-out handling is one of the strongest health-protection controls. Best practices: * Process opt-out requests immediately across all connected sending numbers. * Store consent and opt-out events with timestamp, source, and category. * Confirm the opt-out and provide a clear opt-back-in path. * Offer opt-out controls especially: * In first campaign touchpoints * After long inactivity windows * When messaging inactive audiences Opt-out tracking and enforcement are your responsibility at the application level. Monitor and Act Fast[](https://dualhook.com/docs/account-health#monitor-and-act-fast) -------------------------------------------------------------------------------------- Monitor both real-time events and periodic snapshots. Primary signals: * `phone_number_quality_update` — quality rating changes * `message_template_status_update` — template approval/rejection * Health checks from phone-number fields (`health_status`, `quality_rating`) * Campaign block/report trends See [Webhook Events](https://dualhook.com/docs/webhook-events) and [Health Monitoring](https://dualhook.com/docs/health-monitoring) for implementation details. Quality Lifecycle[](https://dualhook.com/docs/account-health#quality-lifecycle) -------------------------------------------------------------------------------- | Quality | Typical interpretation | Recommended action | | --- | --- | --- | | High / Green | Healthy user satisfaction | Maintain pacing and keep optimizing | | Medium / Yellow | Early dissatisfaction signals | Reduce pressure, improve targeting and content | | Low / Red | High dissatisfaction risk | Pause risky campaigns and remediate immediately | Sustained improvement over several days is typically required before quality state recovers. Incident Playbook[](https://dualhook.com/docs/account-health#incident-playbook) -------------------------------------------------------------------------------- When quality degrades or alerts appear: 1. Pause or throttle high-risk campaigns. 2. Prioritize [utility](https://dualhook.com/docs/utility-messages) and [authentication](https://dualhook.com/docs/authentication-messages) traffic. 3. Remove low-performing templates from active rotation. 4. Tighten audience criteria to recently engaged or opted-in users. 5. Audit opt-out execution for all channels and numbers. 6. Resume [marketing](https://dualhook.com/docs/marketing-messages) gradually after quality stabilizes. Conversation Flow Optimization[](https://dualhook.com/docs/account-health#conversation-flow-optimization) ---------------------------------------------------------------------------------------------------------- Health is strongly influenced by user experience quality. Track: * First response time * Conversation drop-off points * Repeated loops in bot/flow logic * Completion and conversion metrics Continuous optimization: * Test variants of wording and step order * Simplify long flows * Improve handoff to human support when needed See [Messaging Best Practices](https://dualhook.com/docs/messaging-best-practices) for pre-send guidelines. Dualhook surfaces management events and health-related telemetry for monitoring workflows. Use Dualhook monitoring together with Meta account surfaces for full visibility. Message content routes directly from Meta to your webhook endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) . Related ------- * [Messaging Health Status](https://dualhook.com/docs/health-monitoring) Monitor phone number health: AVAILABLE, LIMITED, BLOCKED states and quality rating. * [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) API rate limits, messaging limit tiers, quality rating, and scaling guidance. * [Messaging Best Practices](https://dualhook.com/docs/messaging-best-practices) Opt-in, notification quality, contact validation, callback handling, and pre-launch checklist. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Cloud API Calling: VoIP, BIC/UIC, Architecture | Dualhook Browse Docs Cloud API Calling ================= Voice calling on WhatsApp Business Cloud API: VoIP via WebRTC or SIP, business-initiated and user-initiated calls, prerequisites, availability, and integration patterns. What Cloud API Calling Is[](https://dualhook.com/docs/calling#what-cloud-api-calling-is) ----------------------------------------------------------------------------------------- The **WhatsApp Business Calling API** lets your business initiate and receive voice calls with WhatsApp users over VoIP, using the same business phone number you use for messaging. Calls go through Meta Cloud API and use WebRTC for media — encrypted with SRTP/DTLS, audio compressed with OPUS by default. The same business phone number unifies messaging and calling. From the user's perspective, it's a familiar WhatsApp call — no separate app, no SMS-style trust prompts, no region-specific carrier limitations. Calling is **not enabled by default**. You opt in per phone number by configuring call settings (see [Calling Configuration](https://dualhook.com/docs/calling-configuration) ). Calling Pages in This Section[](https://dualhook.com/docs/calling#calling-pages-in-this-section) ------------------------------------------------------------------------------------------------- * [Calling Configuration](https://dualhook.com/docs/calling-configuration) — Enable calling, call hours, holiday schedule, callback permissions, audio codecs, settings webhooks * [Business-Initiated Calls](https://dualhook.com/docs/calling-business-initiated) — Place outbound calls; `/calls` endpoint with `connect`/`terminate`; lifecycle webhooks * [User-Initiated Calls](https://dualhook.com/docs/calling-user-initiated) — Receive inbound calls; `pre_accept`/`accept`/`reject`/`terminate`; DTMF; companion-device support * [Call Permissions](https://dualhook.com/docs/calling-permissions) — Temporary vs permanent permissions; three ways to obtain them; permission-reply webhooks * [SIP Configuration](https://dualhook.com/docs/calling-sip) — Use SIP signaling instead of Graph API + webhooks; digest auth; SDES vs DTLS * [Call Buttons & Deep Links](https://dualhook.com/docs/calling-call-buttons) — `voice_call` interactive messages, template buttons, `wa.me/call/` deep links * [API Reference](https://dualhook.com/docs/calling-api-reference) — Consolidated endpoints + webhook reference + WebRTC/media implementation guidelines * [Pricing](https://dualhook.com/docs/calling-pricing) — Per-pulse billing, volume tiers, CSW interaction with calling * [Troubleshooting](https://dualhook.com/docs/calling-troubleshooting) — Error codes, audio clipping, media-flow timeouts Two Call Paths: User-Initiated vs Business-Initiated[](https://dualhook.com/docs/calling#two-call-paths-user-initiated-vs-business-initiated) ---------------------------------------------------------------------------------------------------------------------------------------------- | | User-Initiated (UIC) | Business-Initiated (BIC) | | --- | --- | --- | | Who places the call | WhatsApp user | Your business | | Permission required | No (subject to call hours and `call_icon_visibility`) | Yes — explicit user permission needed first | | Availability | Every country where Cloud API is available | All countries except USA, Canada, Egypt, Vietnam, Nigeria (business phone number country) | | Pricing | Free | Per-pulse billed (see [Pricing](https://dualhook.com/docs/calling-pricing)
) | | First webhook | `connect` with SDP offer | None — your business places the call via API; you get a `connect` with SDP answer | The business phone number's country code must be in the BIC-supported list. The recipient's phone can be in any country where Cloud API is available. Two Signaling Paths: Graph API vs SIP[](https://dualhook.com/docs/calling#two-signaling-paths-graph-api-vs-sip) ---------------------------------------------------------------------------------------------------------------- | Configuration | Signaling | Media | | --- | --- | --- | | **Default** (after enabling calling) | Graph API endpoints + webhooks (HTTPS) | WebRTC: ICE + DTLS + SRTP, OPUS codec | | **SIP with WebRTC media** | SIP over TLS | WebRTC: ICE + DTLS + SRTP, OPUS codec | | **SIP with SDES media** | SIP over TLS | SRTP with SDES key exchange (no ICE/DTLS), OPUS + optional PCMA/PCMU | When SIP is enabled, calling-related Graph API endpoints stop working and calling webhooks are not sent — SIP becomes the exclusive signaling path. Messaging continues to use Graph API + webhooks normally. Pick **Graph API + webhooks** unless you have a SIP-based contact center or telephony infrastructure that needs native SIP integration. See [SIP Configuration](https://dualhook.com/docs/calling-sip) for details. Prerequisites[](https://dualhook.com/docs/calling#prerequisites) ----------------------------------------------------------------- Before enabling calling on a business phone number: * The number must be in use with **Cloud API** (not the WhatsApp Business app). * Subscribe your app to the `calls` webhook field (skip this if you're using SIP). * The same app must be subscribed to the WABA of your business phone number. * The app must have `whatsapp_business_messaging` permission for the number. * The phone number must have a **daily messaging limit of at least 2,000** unique recipients. (Test numbers and sandbox accounts bypass this.) * A valid payment method must be attached to the WABA — calls require billing on file even though UIC is free. * Business verification is **not** required for calling (same as messaging). Availability[](https://dualhook.com/docs/calling#availability) --------------------------------------------------------------- **User-initiated calling**: every country where Cloud API is available. iOS, Android, and selected companion devices (iPhone/Android phones registered as additional devices on the same account). Not supported on WhatsApp Web or WhatsApp Desktop. **Business-initiated calling**: every country where Cloud API is available **except** the following business-phone-number countries: * United States * Canada * Egypt * Vietnam * Nigeria The recipient's phone can be from any country where Cloud API is available. Testing and Sandbox[](https://dualhook.com/docs/calling#testing-and-sandbox) ----------------------------------------------------------------------------- Public test numbers (PTNs) and Sandbox accounts have relaxed calling limits to make integration easier. Sandbox accounts are only available to Tech Partners. | Limit | Production | Sandbox / PTN | | --- | --- | --- | | Call permission requests per day (per pair) | 1 | 25 | | Call permission requests per 7 days (per pair) | 2 | 100 | | Consecutive unanswered calls before "reconsider" system message | 2 | 5 | | Consecutive unanswered calls before permission auto-revoked | 4 | 10 | Test numbers also bypass the 2,000-message-limit prerequisite for enabling calling. Calling is **disabled by default** on test numbers — you must configure call settings before placing or receiving calls. Integration Patterns[](https://dualhook.com/docs/calling#integration-patterns) ------------------------------------------------------------------------------- How you wire up calling depends on your role: | Pattern | Number setup | Notes | | --- | --- | --- | | Single BSP for messaging + calling | Existing messaging number extended | Reuse the messaging app and subscribe it to the `calls` webhook field. Recommended for most setups. | | Multi-Solution Conversations (MSC) | Single number, two operators (one for messaging, one for calling) | Both providers operate independently on the same WABA. See Meta's MSC docs. | | Calling-only ISV | New number for calling | ISV hosts Embedded Signup pointing to their own app; end-business pays Meta directly (Tech Provider) or the ISV (BSP). | | Calling Tech Provider with separate Messaging BSP | New calling-exclusive number | BSP and TP create a multi-partner solution; commercial relationship between them is out of band. | Our terms forbid PSTN on any leg of a WhatsApp call. SIP trunks are allowed if they stay exclusively VoIP — bridging into the PSTN is prohibited. Single App vs Multiple Apps[](https://dualhook.com/docs/calling#single-app-vs-multiple-apps) --------------------------------------------------------------------------------------------- You can almost always reuse your existing messaging app for calling. The webhook callback URL is the same; the payload distinguishes message events from call events. Reasons to use one app: * Less app-review and ongoing maintenance overhead. * Simpler footprint on Meta. * The Embedded Signup app and the Graph-API-invoking app stay equal. If you do want separate apps, you can — subscribe the calling app only to `calls`\-related fields and the messaging app only to message-related fields. Override the callback URL per app at WABA or phone-number level. The trade-off is operational complexity, especially for permission management. Dualhook's Stance on Calling[](https://dualhook.com/docs/calling#dualhooks-stance-on-calling) ---------------------------------------------------------------------------------------------- Dualhook does **not** currently expose calling features in its dashboard. Calling is sender-side and media-heavy — your backend handles SDP, WebRTC stacks, and call-state machines, just as you handle outbound messaging today. What Dualhook **does** affect: * The encrypted authorization created through Embedded Signup includes `whatsapp_business_messaging`, which is the same permission needed to place calls and accept inbound calls. Dualhook does not reveal that credential. Calling still requires a supported integration path; the outbound runtime does not expose calling routes. * If you subscribe your app to the `calls` webhook field, those webhooks go directly to your endpoint via [Webhook Override](https://dualhook.com/docs/webhook-override) . Dualhook does not ingest them. * Dualhook does **not** auto-subscribe to `account_settings_update` either. To receive the calling-settings webhook, subscribe your own app to that field at the WABA level — see [Calling Configuration](https://dualhook.com/docs/calling-configuration#settings-webhook-account_settings_update) . If you want Dualhook to surface calling-related state (permission status, call hours, call analytics) in the dashboard, that's a feature request — open it through the in-app support chat. Related ------- * [Calling Configuration](https://dualhook.com/docs/calling-configuration) Enable calling on a business phone number, configure call hours and holidays, restrict the call icon by country, set callback permissions, choose audio codecs, listen for settings webhooks. * [Business-Initiated Calls](https://dualhook.com/docs/calling-business-initiated) Place outbound voice calls to WhatsApp users. POST /calls with action: connect, SDP offer/answer flow, call status webhooks (RINGING/ACCEPTED/REJECTED), terminate webhook with duration, biz\_opaque\_callback\_data tracking. * [User-Initiated Calls](https://dualhook.com/docs/calling-user-initiated) Receive inbound voice calls from WhatsApp users. Call Connect webhook with SDP offer, pre-accept for faster setup, accept/reject/terminate actions, DTMF dial pad, companion-device support. * [Call Permissions](https://dualhook.com/docs/calling-permissions) Obtain permission to call WhatsApp users: temporary (7-day) and permanent permissions, three ways to obtain (free-form request, template request, callback auto-prompt, business profile), get permission state endpoint, permission reply webhook. * [Calling Pricing](https://dualhook.com/docs/calling-pricing) How Meta bills WhatsApp Calling: free user-initiated calls, per-pulse business-initiated billing, volume tiers, customer service window interaction, call permission request charges, billing prerequisites. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Error 131009: Parameter Value Not Valid Fix | Dualhook Browse Docs WhatsApp Error 131009: Parameter Value Not Valid ================================================ WhatsApp error 131009 means a parameter has the right name but a value Meta cannot use: check field formats, supported values, and object IDs. How to read details and fix it. > **TL;DR:** WhatsApp error `131009` ("Parameter value is not valid") means a field in your request has the right name but a value Meta cannot use. It is a request problem, not an account problem. Read `error_data.details` when present, compare values with the endpoint reference, and correct the request before resending. What Error 131009 Means[](https://dualhook.com/docs/whatsapp-error-131009#what-error-131009-means) --------------------------------------------------------------------------------------------------- Meta describes this code as one or more invalid parameter values. HTTP status is `400`. Where error [`100`](https://dualhook.com/docs/whatsapp-error-100) is the general "invalid parameter" code, `131009` is WhatsApp's more specific variant for invalid values. The value may have the wrong format, be outside a supported range, or refer to an unavailable object. The code alone does not identify the field. { "error": { "message": "(#131009) Parameter value is not valid", "type": "OAuthException", "code": 131009, "error_data": { "messaging_product": "whatsapp", "details": "One or more parameter values are invalid." }, "fbtrace_id": "..." } } Where You See It[](https://dualhook.com/docs/whatsapp-error-131009#where-you-see-it) ------------------------------------------------------------------------------------- An immediate API error on the send or management call. On the Dualhook Runtime API the Meta error is relayed unchanged with an added `X-Dualhook-Request-Id`. Also monitor webhook failures because a successful send response does not guarantee delivery. Runtime health emails count HTTP request failures, not later delivery failures. Common Causes[](https://dualhook.com/docs/whatsapp-error-131009#common-causes) ------------------------------------------------------------------------------- Use the error details to decide which values to check. These are validation checks, not guarantees that every mistake returns this particular code: * **Field formats and limits.** Check supported enum values, maximum lengths, media objects, and template parameter types against the endpoint reference. * **Phone numbers and IDs.** Confirm international phone-number formatting, the sender's registration, and that IDs refer to the intended object and account. * **Reactions.** Use the exact message ID received in the webhook and a valid emoji. An old or unavailable reaction target does not by itself prove error 131009; inspect the actual response and status webhook. * **Marketing Messages API.** Meta separately documents incomplete ad syncing as a possible cause of 131009 on this API, with a 10-minute wait before retrying. That exception is not a general retry rule for Cloud API sends. How to Fix It[](https://dualhook.com/docs/whatsapp-error-131009#how-to-fix-it) ------------------------------------------------------------------------------- 1. Read `error_data.details` when present and keep the full error object for diagnosis. 2. Compare the named value with the endpoint reference. Check format, supported values, and length limits. 3. For message IDs, use the exact value from the webhook or send response. For phone numbers and other assets, confirm the correct account and sender. 4. Correct the request before resending. Only apply the 10-minute retry exception when using the Marketing Messages API and incomplete ad syncing is the likely cause. How to Prevent It[](https://dualhook.com/docs/whatsapp-error-131009#how-to-prevent-it) --------------------------------------------------------------------------------------- * Validate each message type against its own schema, including permitted values and lengths. * Store message IDs verbatim and associate them with the correct connection and recipient. * Log the HTTP response and webhook failure statuses, including error details when supplied. Related Errors[](https://dualhook.com/docs/whatsapp-error-131009#related-errors) --------------------------------------------------------------------------------- * [`/docs/whatsapp-error-100`](https://dualhook.com/docs/whatsapp-error-100) : the general invalid-parameter code. * [`/docs/whatsapp-error-131008`](https://dualhook.com/docs/whatsapp-error-131008) : a required parameter is missing. * [`/docs/whatsapp-error-132000`](https://dualhook.com/docs/whatsapp-error-132000) : template variable count mismatch. * [See the full error reference](https://dualhook.com/docs/api-errors) FAQ[](https://dualhook.com/docs/whatsapp-error-131009#faq) ----------------------------------------------------------- **What is WhatsApp error 131009?** One or more parameter values are invalid. Compare the values with the endpoint reference and read error\_data.details when supplied. **Is 131009 the same as error 100?** They overlap. Error 100 commonly concerns unsupported or misspelled parameters; 131009 specifically reports invalid values. Neither code alone identifies the offending field. **Can I retry 131009?** Correct the invalid value first. For the Marketing Messages API only, Meta also documents incomplete ad syncing and recommends waiting 10 minutes before retrying that case. **Why does a reaction fail with 131009?** Check the reaction parameters and error details, including the message ID and emoji. Do not infer this code solely from the target message being old or unavailable. How Dualhook Helps[](https://dualhook.com/docs/whatsapp-error-131009#how-dualhook-helps) ----------------------------------------------------------------------------------------- Dualhook relays Meta's `131009` unchanged, including `error_data.details` and `fbtrace_id`, so the diagnosis is in the response. If a connection keeps receiving `131009` on sends, the organization's admins receive a runtime health email pointing here. Dualhook never retries sends on your behalf; see [Runtime API Errors](https://dualhook.com/docs/runtime-api-errors) for the runtime-side failures that sit next to this one. Sources: [Meta error codes, including Marketing Messages API errors](https://developers.facebook.com/documentation/business-messaging/whatsapp/support/error-codes/) and [reaction messages](https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/reaction-messages/) . Checked September 6, 2026. Related ------- * [WhatsApp Error 100: Invalid Parameter](https://dualhook.com/docs/whatsapp-error-100) WhatsApp error 100 means Meta rejected a value or field in your request. Read error\_data.details, fix the recipient format, message type, or template component, and resend. * [WhatsApp Error 131008: Required Parameter Is Missing](https://dualhook.com/docs/whatsapp-error-131008) WhatsApp error 131008 means the request is missing a required parameter — common with flow buttons and dynamic template buttons. Causes and step-by-step fixes. * [Runtime API Errors](https://dualhook.com/docs/runtime-api-errors) Troubleshoot Dualhook Runtime API failures by symptom: revoked keys, rejected media, wrong-connection scope, unsupported fields, validation, rate limits, and the send failure circuit. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Dualhook + n8n: WhatsApp Webhook Receiver, Meta Verification, and Template Sends | Dualhook Browse Docs n8n Webhook Integration ======================= Use n8n as a direct WhatsApp webhook receiver with Meta verification, payload-shape validation, and outbound sends through Dualhook's runtime. Prerequisites[](https://dualhook.com/docs/guide-n8n-webhook#prerequisites) --------------------------------------------------------------------------- * An active [Dualhook connection](https://dualhook.com/docs/getting-started) with [Webhook Override](https://dualhook.com/docs/webhook-override) enabled * An n8n Cloud or self-hosted instance with a workflow you can publish * Your Meta webhook verify token (for the GET handshake — set during connection setup in Dualhook) * A per-connection Dualhook `dh_live_...` key and `PHONE_NUMBER_ID` for outbound runtime sends * Your `WABA_ID` for payload-side validation Keep these values in n8n credentials or environment-backed variables rather than hardcoding them into nodes: WEBHOOK_VERIFY_TOKEN DUALHOOK_API_KEY PHONE_NUMBER_ID WABA_ID META_GRAPH_VERSION=v25.0 > **No `META_APP_SECRET`?** In the Embedded Signup flow `X-Hub-Signature-256` is signed by Dualhook's Meta app and isn't customer-verifiable — validate inbound webhook payload shape instead. See [Messaging Webhook → Inbound Webhook POST Validation](https://dualhook.com/docs/messaging-webhook#signature-validation-post) > . Architecture Overview[](https://dualhook.com/docs/guide-n8n-webhook#architecture-overview) ------------------------------------------------------------------------------------------- With Dualhook, Meta sends message-path webhooks directly to your n8n webhook URL. Dualhook stays out of the message content path and focuses on setup, monitoring, and management events. Meta (messages, statuses, errors) → n8n Webhook URL → verification → inbound payload validation → business workflow → optional send through api.dualhook.com Dualhook → management events, health visibility, template operations n8n becomes part of your message-processing boundary. If you keep execution history, pinned data, or verbose logs enabled, message content can be retained in n8n even though Dualhook itself does not store it. See [Compliance & Data Retention](https://dualhook.com/docs/compliance) and [Architecture & Security](https://dualhook.com/docs/architecture-security) for the privacy split. Build the Webhook Workflow[](https://dualhook.com/docs/guide-n8n-webhook#build-the-webhook-workflow) ----------------------------------------------------------------------------------------------------- Create one production workflow for your Meta callback path. 1. Add a **Webhook** node. 2. Set a stable path such as `meta/messages`. 3. If you want GET verification and POST events on the same path, enable **Allow Multiple HTTP Methods** and accept both `GET` and `POST`. 4. Set **Respond** to **Using "Respond to Webhook" Node**. 5. Add the **Raw Body** option so n8n keeps the exact bytes Meta signed. 6. Add the **Binary Property** option and set a clear name such as `rawBody`. 7. Publish the workflow and use the **Production URL** in Dualhook. Do not use the temporary Test URL for real traffic. The simplest branch layout is: * `GET` branch for Meta verification * `POST` branch for inbound payload validation and event processing Handle the Verification Handshake[](https://dualhook.com/docs/guide-n8n-webhook#handle-the-verification-handshake) ------------------------------------------------------------------------------------------------------------------- Meta verifies your callback URL with a `GET` request that includes: * `hub.mode=subscribe` * `hub.verify_token=` * `hub.challenge=` In the `GET` branch: 1. Check that `hub.mode` is `subscribe`. 2. Compare `hub.verify_token` with your expected verify token. 3. If both match, return the raw `hub.challenge` value with a **Respond to Webhook** node. 4. If they do not match, return `403`. Recommended response settings: * Success: `200`, plain text body = `hub.challenge` * Failure: `403`, plain text body = `Forbidden` Dualhook uses the same verify token you entered during connection setup, so the value in n8n and the value stored on the Dualhook connection must match. Validate the Webhook Payload[](https://dualhook.com/docs/guide-n8n-webhook#validate-the-webhook-payload) --------------------------------------------------------------------------------------------------------- Validate every `POST` before you process the payload. Confirm the WhatsApp envelope shape and the WABA ID / `phone_number_id` against the values you stored for this connection. ### Check the envelope, WABA, and phone number[](https://dualhook.com/docs/guide-n8n-webhook#check-the-envelope-waba-and-phone-number) In the `POST` branch, add an **IF** (or **Code**) node that rejects anything that does not look like the expected payload: * `object === "whatsapp_business_account"` * `entry[0].id === $env.WABA_ID` * `entry[0].changes[0].field === "messages"` * `entry[0].changes[0].value.metadata.phone_number_id` is present and equals `$env.PHONE_NUMBER_ID` If any check fails, respond `401` with a **Respond to Webhook** node and stop the workflow. ### Keep the URL unguessable[](https://dualhook.com/docs/guide-n8n-webhook#keep-the-url-unguessable) Use n8n's randomly generated webhook path. Treat that path as a secret: do not paste it into shared chat, do not commit it to repos, and rotate it (publish the workflow to a new path) if it leaks. ### Respond early[](https://dualhook.com/docs/guide-n8n-webhook#respond-early) For valid payloads: 1. Return `200` as soon as the envelope check passes. 2. Continue the heavy work after the response step. For payloads that fail validation: * Return `401` * Stop processing This keeps webhook acknowledgments fast enough for Meta retries to stay rare. Send Template Messages via Dualhook[](https://dualhook.com/docs/guide-n8n-webhook#send-template-messages-via-dualhook) ----------------------------------------------------------------------------------------------------------------------- n8n sends outbound messages through Dualhook's allowlisted runtime. Inbound routing remains direct from Meta. Use an **HTTP Request** node with a Bearer-auth credential: * Method: `POST` * URL: `https://api.dualhook.com/v25.0//messages` * Authentication: Bearer auth * Header: `Content-Type: application/json` Example template send: { "messaging_product": "whatsapp", "to": "12015550123", "type": "template", "template": { "name": "order_update_v1", "language": { "code": "en_US" } } } For variables, media headers, and button parameters, see [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) . Test with Dualhook[](https://dualhook.com/docs/guide-n8n-webhook#test-with-dualhook) ------------------------------------------------------------------------------------- 1. Publish the n8n workflow so the Production URL is active. 2. Put that Production URL in Dualhook as the webhook URL. 3. Use the same verify token in Dualhook and in your n8n verification branch. 4. Run Dualhook **Test Ping** to confirm reachability. 5. Send a real WhatsApp message to your number. 6. Confirm that: * verification succeeds * inbound payload validation accepts the real Meta POST and rejects test pings with the wrong shape * the workflow returns `200` quickly * both inbound messages and `statuses` events are handled Production Caveats[](https://dualhook.com/docs/guide-n8n-webhook#production-caveats) ------------------------------------------------------------------------------------- * n8n execution history can store message content. Review retention, logging, and pinning settings if you want to preserve Dualhook's minimal-storage posture downstream. * Use n8n credentials instead of pasting secrets into node fields. In self-hosted production, set `N8N_ENCRYPTION_KEY`. If you run n8n Enterprise, external secrets are worth considering. * Use idempotency in downstream processing. A practical pattern is deduping inbound messages on `wamid` and statuses on `(message_id, status)`. * Treat WhatsApp webhook payloads as evolving. With Meta's BSUID rollout and later username launch, some fields are conditional. Parse defensively and prefer field presence checks over rigid assumptions. See the [BSUID webhook changes reference](https://dualhook.com/docs/contacts-bsuid-webhooks) . * If you deactivate or republish the workflow in a way that changes availability, Meta deliveries will fail until the Production URL is healthy again. Related ------- * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. * [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) How to send template messages via Cloud API with variables, media headers, and URL buttons. * [Compliance & Data Retention](https://dualhook.com/docs/compliance) Privacy boundary, configurable retention, CSV export, and organization isolation. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Node.js WhatsApp Webhook Receiver with Dualhook | Dualhook Browse Docs Node.js Webhook Receiver ======================== Set up a Node.js Express endpoint to receive WhatsApp webhooks routed by Dualhook. Prerequisites[](https://dualhook.com/docs/guide-nodejs-webhook#prerequisites) ------------------------------------------------------------------------------ * Node.js 18+ installed * A WhatsApp Business Account connected through [Dualhook](https://dualhook.com/docs/getting-started) * [Webhook Override](https://dualhook.com/docs/webhook-override) configured so Meta delivers messages directly to your server * Your webhook verify token (for Meta's GET handshake — set during connection setup in Dualhook) * A connection-scoped Dualhook API key for outbound sends Set these environment variables: WEBHOOK_VERIFY_TOKEN=your_verify_token DUALHOOK_API_KEY=dh_live_your_key PHONE_NUMBER_ID=your_phone_number_id WABA_ID=your_waba_id > **No `META_APP_SECRET`?** In the Embedded Signup flow `X-Hub-Signature-256` is signed by Dualhook's Meta app and isn't customer-verifiable — validate inbound webhook payload shape instead. See [Messaging Webhook → Inbound Webhook POST Validation](https://dualhook.com/docs/messaging-webhook#signature-validation-post) > . Create a Webhook Endpoint[](https://dualhook.com/docs/guide-nodejs-webhook#create-a-webhook-endpoint) ------------------------------------------------------------------------------------------------------ Set up an Express.js server that handles both the GET verification challenge and POST webhook events. Meta requires a quick 200 response (within ~10 seconds) on POST webhooks and will retry on non-2xx responses. import express from "express"; const app = express(); app.use(express.json()); const VERIFY_TOKEN = process.env.WEBHOOK_VERIFY_TOKEN; const EXPECTED_PHONE_NUMBER_ID = process.env.PHONE_NUMBER_ID; const EXPECTED_WABA_ID = process.env.WABA_ID; // GET — Meta sends this to verify your endpoint app.get("/webhook", (req, res) => { const mode = req.query["hub.mode"]; const token = req.query["hub.verify_token"]; const challenge = req.query["hub.challenge"]; if (mode === "subscribe" && token === VERIFY_TOKEN) { return res.status(200).send(challenge); } return res.sendStatus(403); }); // POST — Meta sends message and status webhooks here app.post("/webhook", (req, res) => { if (!isExpectedPayload(req.body)) { return res.sendStatus(401); } // Always respond 200 quickly to avoid Meta retries res.sendStatus(200); // Process the webhook payload asynchronously handleWebhook(req.body); }); app.listen(3000, () => console.log("Webhook server running on port 3000")); Validate the Webhook Payload[](https://dualhook.com/docs/guide-nodejs-webhook#validate-the-webhook-payload) ------------------------------------------------------------------------------------------------------------ Validate the envelope, WABA ID, and `phone_number_id` against the values you stored for this connection, and serve the endpoint on a private, high-entropy URL path. function isExpectedPayload(body) { if (!body || body.object !== "whatsapp_business_account") return false; if (!Array.isArray(body.entry) || body.entry.length === 0) return false; for (const entry of body.entry) { if (entry.id !== EXPECTED_WABA_ID) return false; if (!Array.isArray(entry.changes) || entry.changes.length === 0) return false; for (const change of entry.changes) { if (change.field !== "messages") return false; const phoneNumberId = change.value?.metadata?.phone_number_id; if (phoneNumberId !== EXPECTED_PHONE_NUMBER_ID) return false; } } return true; } Handle Incoming Messages[](https://dualhook.com/docs/guide-nodejs-webhook#handle-incoming-messages) ---------------------------------------------------------------------------------------------------- Parse the webhook payload to extract message content. Meta sends a nested structure with changes grouped by phone number. function handleWebhook(body) { if (body.object !== "whatsapp_business_account") return; for (const entry of body.entry ?? []) { for (const change of entry.changes ?? []) { if (change.field !== "messages") continue; const value = change.value; const phoneNumberId = value.metadata?.phone_number_id; // Handle incoming messages for (const message of value.messages ?? []) { console.log(`Message from ${message.from}: ${message.text?.body}`); // Route to your application logic here } // Handle delivery statuses for (const status of value.statuses ?? []) { console.log(`Status update: ${status.id} → ${status.status}`); } } } } Send a Template Message[](https://dualhook.com/docs/guide-nodejs-webhook#send-a-template-message) -------------------------------------------------------------------------------------------------- Send template messages through Dualhook's allowlisted runtime. Inbound webhooks still arrive directly from Meta. The `to` field must be in E.164 format (e.g. `+14155552671`). WhatsApp rejects numbers that are not E.164-formatted. const PHONE_NUMBER_ID = process.env.PHONE_NUMBER_ID; const DUALHOOK_API_KEY = process.env.DUALHOOK_API_KEY; async function sendTemplate(to, templateName, languageCode = "en") { const response = await fetch( `https://api.dualhook.com/v25.0/${PHONE_NUMBER_ID}/messages`, { method: "POST", headers: { Authorization: `Bearer ${DUALHOOK_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ messaging_product: "whatsapp", to, type: "template", template: { name: templateName, language: { code: languageCode }, }, }), } ); const data = await response.json(); if (!response.ok) { console.error("Send failed:", data.error); } return data; } For templates with variables and media headers, see the [Sending Template Messages](https://dualhook.com/docs/sending-template-messages) documentation. Test with Dualhook[](https://dualhook.com/docs/guide-nodejs-webhook#test-with-dualhook) ---------------------------------------------------------------------------------------- 1. Connect your number through [Embedded Signup](https://dualhook.com/docs/embedded-signup) — Dualhook configures the webhook URL via Meta's Webhook Override automatically. 2. Set your server's URL as the webhook endpoint in the Dualhook dashboard. 3. Send a test message to your WhatsApp number. The message webhook arrives directly at your Express endpoint from Meta. 4. Check Dualhook's [webhook activity logs](https://dualhook.com/docs/basics-overview) to verify management events are being received for operational monitoring. Inbound messages flow from Meta directly to your server. Outbound payloads transit Dualhook's runtime without content persistence or caching. Related ------- * [WhatsApp Webhook Override](https://dualhook.com/docs/webhook-override) How Dualhook uses WhatsApp Webhook Override to route supported customer-path webhooks directly from Meta to your server. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. * [The Basics Overview](https://dualhook.com/docs/basics-overview) Base URL, authentication, message send payloads, and webhook subscription basics. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Call Buttons & Deep Links | Dualhook Browse Docs Call Buttons & Deep Links ========================= Drive WhatsApp calls from messages and links: voice\_call interactive messages, voice\_call template buttons, wa.me/call/ deep links with biz\_payload tracking. What This Page Covers[](https://dualhook.com/docs/calling-call-buttons#what-this-page-covers) ---------------------------------------------------------------------------------------------- Three ways to surface WhatsApp calling to your customers: 1. **Interactive `voice_call` message** — free-form message during an open CSW with a "Call on WhatsApp" button. 2. **`voice_call` template button** — same UX, but as a template button so you can send outside an open CSW. 3. **`wa.me/call/` deep link** — shareable link you embed on your website, app, QR code, or in WhatsApp messages themselves. All three trigger a [user-initiated call](https://dualhook.com/docs/calling-user-initiated) when tapped, so your endpoint receives a Call Connect webhook with `direction: USER_INITIATED`. The button/link can also carry a payload string that's echoed back on the call webhooks (`cta_payload` for buttons, `deeplink_payload` for deep links). Interactive voice\_call Message (Free-Form)[](https://dualhook.com/docs/calling-call-buttons#interactive-voice_call-message-free-form) --------------------------------------------------------------------------------------------------------------------------------------- Send during an open CSW (or any open conversation window). Standard message-status webhook fires. POST //messages { "messaging_product": "whatsapp", "recipient_type": "individual", "to": "12015550123", "type": "interactive", "interactive": { "type": "voice_call", "body": { "text": "You can call us on WhatsApp now for faster service!" }, "action": { "name": "voice_call", "parameters": { "display_text": "Call on WhatsApp", "ttl_minutes": 100, "payload": "support_v2_button" } } } } | Field | Default | Notes | | --- | --- | --- | | `display_text` | "Call Now" | Max 20 chars | | `ttl_minutes` | 10080 (7 days) | 1 to 43200 (30 days) — button stops working after this | | `payload` | — | Up to 512 chars. Echoed back on call webhooks as `cta_payload`. Cloud API doesn't process it. | Sending to users on older app versions returns error `131026`. voice\_call Template Button[](https://dualhook.com/docs/calling-call-buttons#voice_call-template-button) --------------------------------------------------------------------------------------------------------- Use a template if you want to send outside a CSW or want the message reusable. The button can sit alongside other template buttons (URL, etc.). **Create the template:** POST //message_templates { "name": "wa_voice_call", "category": "MARKETING", "language": "en", "components": [\ {\ "type": "BODY",\ "text": "You can call us on WhatsApp now for faster service!"\ },\ {\ "type": "BUTTONS",\ "buttons": [\ {\ "type": "voice_call",\ "text": "Call Now",\ "ttl_minutes": 1440\ },\ {\ "type": "URL",\ "text": "Contact Support",\ "url": "https://example.com/support"\ }\ ]\ }\ ] } Template button-level `ttl_minutes` ranges from **1440 (1 day) to 43200 (30 days)**. The free-form message TTL minimum is 1 minute — templates require a higher floor. The send-time TTL can override the template-level value. Send the Template[](https://dualhook.com/docs/calling-call-buttons#send-the-template) -------------------------------------------------------------------------------------- POST //messages { "to": "12015550123", "messaging_product": "whatsapp", "type": "template", "recipient_type": "individual", "template": { "name": "wa_voice_call", "language": { "code": "en" }, "components": [\ {\ "type": "button",\ "sub_type": "voice_call",\ "parameters": [\ { "type": "ttl_minutes", "ttl_minutes": 100 },\ { "type": "payload", "payload": "campaign_q4_winback" }\ ]\ }\ ] } } Per-send `ttl_minutes` ranges from **1 to 43200** (default 10080). `payload` (max 512 chars) is echoed as `cta_payload` on call webhooks. Calling Deep Links (wa.me/call)[](https://dualhook.com/docs/calling-call-buttons#calling-deep-links-wamecall) -------------------------------------------------------------------------------------------------------------- Deep links route a click directly into a WhatsApp call to your business. Format: wa.me/call/ Example: `wa.me/call/15550783881` Use them anywhere a clickable URL fits — your website, in-app links, email signatures, QR codes for printed materials. Since they're per-business-phone-number, you can send a deep link inside a WhatsApp message to point users at a _different_ business phone number than the one they're chatting with. **Note:** Deep links are **not supported on WhatsApp Desktop** clients. They work on Android and iOS WhatsApp apps. Deep Link Payloads (biz\_payload)[](https://dualhook.com/docs/calling-call-buttons#deep-link-payloads-biz_payload) ------------------------------------------------------------------------------------------------------------------- Append a `biz_payload` query string to attach an arbitrary tracking string: wa.me/call/15550783881?biz_payload=campaign_winter_2026 The payload comes back as `deeplink_payload` on the Call Connect and Terminate webhooks. Cloud API doesn't process it. Available on WhatsApp client version **2.25.27 or later**. Where Payloads Arrive in Webhooks[](https://dualhook.com/docs/calling-call-buttons#where-payloads-arrive-in-webhooks) ---------------------------------------------------------------------------------------------------------------------- Both `cta_payload` (from buttons) and `deeplink_payload` (from deep links) appear on the `calls[]` object in: * The Call Connect webhook for the user-initiated call * The Call Terminate webhook for the same call Use them to attribute calls back to the campaign / page / button / template that drove them. "calls": [\ {\ "id": "wacid.HBgL...",\ "direction": "USER_INITIATED",\ "event": "connect",\ "cta_payload": "support_v2_button",\ "deeplink_payload": "campaign_winter_2026"\ }\ ] If a call originated from a button: `cta_payload` set, `deeplink_payload` absent. Deep link: vice versa. Native call (call icon, contact-app dial): both absent. For SIP setups, the same payloads come through as custom SIP headers `x-wa-meta-cta-payload` and `x-wa-meta-deeplink-payload` on the INVITE. Client Version Requirements[](https://dualhook.com/docs/calling-call-buttons#client-version-requirements) ---------------------------------------------------------------------------------------------------------- | Feature | Min WhatsApp client version | | --- | --- | | `voice_call` button | Android 2.24.1+ | | Call icon visibility setting | Android & iOS 2.24.10.8+ | | `payload` on `voice_call` button (interactive + template) | 2.25.27+ | | `biz_payload` on `wa.me/call/` deep link | 2.25.27+ | | Permission requests + business-initiated calling | Android & iOS 2.24.14+ | Sending a `voice_call` button to a user on an older client falls back to either no button or a different render — handle gracefully and offer a chat fallback. Related ------- * [User-Initiated Calls](https://dualhook.com/docs/calling-user-initiated) Receive inbound voice calls from WhatsApp users. Call Connect webhook with SDP offer, pre-accept for faster setup, accept/reject/terminate actions, DTMF dial pad, companion-device support. * [Business-Initiated Calls](https://dualhook.com/docs/calling-business-initiated) Place outbound voice calls to WhatsApp users. POST /calls with action: connect, SDP offer/answer flow, call status webhooks (RINGING/ACCEPTED/REJECTED), terminate webhook with duration, biz\_opaque\_callback\_data tracking. * [Marketing Messages](https://dualhook.com/docs/marketing-messages) Marketing template messaging through Dualhook's standard Cloud API runtime, plus boundaries for Meta's separate MM API product. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp User-Initiated Calls: Inbound Voice with Pre-Accept | Dualhook Browse Docs User-Initiated Calls ==================== Receive inbound voice calls from WhatsApp users. Call Connect webhook with SDP offer, pre-accept for faster setup, accept/reject/terminate actions, DTMF dial pad, companion-device support. Overview[](https://dualhook.com/docs/calling-user-initiated#overview) ---------------------------------------------------------------------- User-initiated calls (UIC) let WhatsApp users place voice calls to your business. UIC is **free** for the business and available in every country where Cloud API is available — no permission required. You control when calls are accepted via [`call_hours`](https://dualhook.com/docs/calling-configuration#call_hours) and whether the call button is visible via [`call_icon_visibility`](https://dualhook.com/docs/calling-configuration#call_icon_visibility) . Consumer Device Eligibility[](https://dualhook.com/docs/calling-user-initiated#consumer-device-eligibility) ------------------------------------------------------------------------------------------------------------ UIC currently accepts calls only from **iPhone and Android phones** — including: * The user's primary device (their main account device) * Companion devices that are iPhone or Android phones WhatsApp Web, Desktop, tablets, and smart-glass companions are **not supported** for UIC. If a user attempts to call from those, the call won't reach your endpoint. `callback_permission_status` enabled does **not** work on companion devices yet — only the primary device flow honors it. Prerequisites[](https://dualhook.com/docs/calling-user-initiated#prerequisites) -------------------------------------------------------------------------------- * Subscribe your app to the `calls` webhook field. * Calling enabled on the business phone number. * (Optional but recommended) `call_hours` configured if you have specific operating hours. * (Optional) `callback_permission_status: ENABLED` if you want auto-permission for callback calls. The UIC Flow[](https://dualhook.com/docs/calling-user-initiated#the-uic-flow) ------------------------------------------------------------------------------ 1. **User taps call button** on your business profile (or via [Call Buttons & Deep Links](https://dualhook.com/docs/calling-call-buttons) ). 2. **Call Connect webhook** arrives at your endpoint with an SDP offer from Meta. 3. **Pre-accept** (recommended): `POST /calls action: pre_accept` with your SDP answer. Establishes the WebRTC connection without yet flowing call media. 4. **Accept**: once your WebRTC stack is ready, `POST /calls action: accept`. Wait for the 200 OK before sending media. 5. **Terminate**: either party hangs up. You receive a Call Terminate webhook. You have about **30–60 seconds** after the Call Connect webhook to accept. If you don't, the call ends on the user's side with "Not Answered" and you receive a Terminate webhook. Pre-Accept (Recommended)[](https://dualhook.com/docs/calling-user-initiated#pre-accept-recommended) ---------------------------------------------------------------------------------------------------- Pre-accepting establishes the media connection ahead of time, avoiding the audio clipping that happens when media flows before the WebRTC channel is ready (caller misses the first few words). POST //calls { "messaging_product": "whatsapp", "call_id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA", "action": "pre_accept", "session": { "sdp_type": "answer", "sdp": "<>" } } Success response: { "success": true } After pre-accept, your WebRTC stack should be initiating ICE connectivity checks. Don't send media yet — wait until you've called `accept` and received the 200 OK. **Note:** if your SDP answer in `accept` doesn't match the one you sent in `pre_accept` (same `call_id`), you'll get an error. Send the same SDP both times. If you can't control when your media starts flowing (e.g. some IVR systems start playing immediately on connect), skip pre-accept and go straight to `accept`. Pre-accept's whole point is to frontload connection setup, but it requires you to control media timing. Accept the Call[](https://dualhook.com/docs/calling-user-initiated#accept-the-call) ------------------------------------------------------------------------------------ { "messaging_product": "whatsapp", "call_id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA", "action": "accept", "session": { "sdp_type": "answer", "sdp": "<>" }, "biz_opaque_callback_data": "0fS5cePMok" } Wait for the 200 OK before transmitting call media. If pre-accept was used, the WebRTC connection is already up — media flows immediately. Otherwise, give the connection time to negotiate before pushing audio. `biz_opaque_callback_data` is optional. Cloud API doesn't process it; it's echoed back in the subsequent Terminate webhook for your own correlation. Up to 512 chars. Reject the Call[](https://dualhook.com/docs/calling-user-initiated#reject-the-call) ------------------------------------------------------------------------------------ { "messaging_product": "whatsapp", "call_id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA", "action": "reject" } A reject also triggers the Call Terminate webhook with `direction: USER_INITIATED` and `status: COMPLETED` or `FAILED`. Terminate[](https://dualhook.com/docs/calling-user-initiated#terminate) ------------------------------------------------------------------------ { "messaging_product": "whatsapp", "call_id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA", "action": "terminate" } Always call terminate even if you sent an RTCP BYE — Meta uses this for accurate billing. You don't need to call it when the user hangs up; you'll just receive the Call Terminate webhook. Call Connect Webhook (SDP Offer)[](https://dualhook.com/docs/calling-user-initiated#call-connect-webhook-sdp-offer) -------------------------------------------------------------------------------------------------------------------- { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "calls",\ "value": {\ "messaging_product": "whatsapp",\ "metadata": {\ "display_phone_number": "15550001234",\ "phone_number_id": "123456789012345"\ },\ "contacts": [\ {\ "profile": { "name": "Pablo Morales" },\ "wa_id": "12015550123"\ }\ ],\ "calls": [\ {\ "id": "wacid.HBgLMTIxODU1NTI4MjgVAgARGCAyODRQIAFRoA",\ "to": "15550001234",\ "from": "12015550123",\ "event": "connect",\ "direction": "USER_INITIATED",\ "timestamp": "1671644824",\ "deeplink_payload": "campaign_id_42",\ "cta_payload": "support_button_v2",\ "session": {\ "sdp_type": "offer",\ "sdp": "<>"\ }\ }\ ]\ }\ }\ ]\ }\ ] } Apply the SDP offer to your WebRTC stack to generate your SDP answer, then `pre_accept` or `accept` with that answer. Call Terminate Webhook[](https://dualhook.com/docs/calling-user-initiated#call-terminate-webhook) -------------------------------------------------------------------------------------------------- Same envelope as BIC's terminate webhook but with `direction: USER_INITIATED`. See [Business-Initiated → Call Terminate Webhook](https://dualhook.com/docs/calling-business-initiated#call-terminate-webhook) for the field reference. UIC terminates can also include `deeplink_payload` and `cta_payload` if the call originated from a deep link or CTA button. deeplink\_payload and cta\_payload[](https://dualhook.com/docs/calling-user-initiated#deeplink_payload-and-cta_payload) ------------------------------------------------------------------------------------------------------------------------ Two arbitrary payload strings come through on UIC webhooks when the call originated from a Dualhook-instrumented entry point: | Field | Source | Set on | | --- | --- | --- | | `cta_payload` | `payload` field on a [`voice_call` interactive message or template button](https://dualhook.com/docs/calling-call-buttons) | When user taps the call button on a message you sent | | `deeplink_payload` | `biz_payload` query parameter on `wa.me/call/?biz_payload=...` | When user clicks your deep link | Both are echoed back in **both** the Call Connect webhook and the Call Terminate webhook. Use them to correlate the call with the campaign / page / button that drove it. Available on WhatsApp client version 2.25.27+. DTMF (Dial Pad) Support[](https://dualhook.com/docs/calling-user-initiated#dtmf-dial-pad-support) -------------------------------------------------------------------------------------------------- WhatsApp client apps include a dial pad during calls with Cloud API business numbers. Users press tone buttons; the DTMF tones are injected into the WebRTC RTP stream per RFC 4733. | Property | Value | | --- | --- | | Clock rate | **8,000 Hz only** (48,000 not supported) | | Payload type | 126 (default if absent in your SDP) | | Timestamp / sequence base | Same as regular audio packets | | Webhook for DTMF digits | **None** — DTMF is in-band only | For BIC, your SDP offer should include 8000 clock rate. For UIC, Meta's SDP offer already has it. The dial pad supports IVR-style flows but doesn't support consumer-to-consumer calls or initiating new calls / messages. Common Errors[](https://dualhook.com/docs/calling-user-initiated#common-errors) -------------------------------------------------------------------------------- | Code | Meaning | Fix | | --- | --- | --- | | `138000` | Calling not enabled | Configure call settings ([Calling Configuration](https://dualhook.com/docs/calling-configuration)
) | | `138019` | Call setup failed (sent in terminate webhook) | Retry; check media path | | `138020` | Relay connection failed (sent in terminate webhook) | Retry; check network | | `138021` | Media receive timeout (no media for ~20–30s) | Confirm your media server is sending RTP packets | | `138022` | Media transmit timeout | Verify outbound media path | | `138023` | Call accepted but terminated with no media signals | Investigate WebRTC setup | | `131044` | No valid payment method on user-initiated call | Attach a payment method | | `131055` | Graph API call attempted on a SIP-enabled number | Use SIP signaling instead | Full error reference on [Calling Troubleshooting](https://dualhook.com/docs/calling-troubleshooting) . Related ------- * [Cloud API Calling](https://dualhook.com/docs/calling) Voice calling on WhatsApp Business Cloud API: VoIP via WebRTC or SIP, business-initiated and user-initiated calls, prerequisites, availability, and integration patterns. * [Calling Configuration](https://dualhook.com/docs/calling-configuration) Enable calling on a business phone number, configure call hours and holidays, restrict the call icon by country, set callback permissions, choose audio codecs, listen for settings webhooks. * [Call Buttons & Deep Links](https://dualhook.com/docs/calling-call-buttons) Drive WhatsApp calls from messages and links: voice\_call interactive messages, voice\_call template buttons, wa.me/call/ deep links with biz\_payload tracking. * [Calling Troubleshooting](https://dualhook.com/docs/calling-troubleshooting) WhatsApp Calling errors and fixes: full error code table (138000-138023), SIP-specific status codes, media-flow timeouts, audio clipping causes and mitigations (SDES, pre-accept), quick triage guide. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Calling Troubleshooting: Error Codes & Audio Clipping | Dualhook Browse Docs Calling Troubleshooting ======================= WhatsApp Calling errors and fixes: full error code table (138000-138023), SIP-specific status codes, media-flow timeouts, audio clipping causes and mitigations (SDES, pre-accept), quick triage guide. What This Page Covers[](https://dualhook.com/docs/calling-troubleshooting#what-this-page-covers) ------------------------------------------------------------------------------------------------- Error codes for the WhatsApp Business Calling API plus media-path issues (audio clipping, RTP timeouts) that don't surface as clean error codes. SIP-specific errors are also collected here for one-stop reference. Standard Error Shape[](https://dualhook.com/docs/calling-troubleshooting#standard-error-shape) ----------------------------------------------------------------------------------------------- Most errors return: { "error": { "message": "", "type": "", "code": , "fbtrace_id": "" } } Some errors include `error_data.details` with extra context. Always log `code`, `message`, and `fbtrace_id` — Meta support needs the trace ID for incident investigation. For terminate webhooks on failed calls, the `errors[]` array on the `calls` object carries the same shape. Call Logs in WhatsApp Manager[](https://dualhook.com/docs/calling-troubleshooting#call-logs-in-whatsapp-manager) ----------------------------------------------------------------------------------------------------------------- The Call Logs tab in WhatsApp Manager (Account tools → Phone numbers → select number → Call Logs) shows a per-call event timeline. Useful for self-service troubleshooting and reproducing the exact event sequence. | Field | Description | | --- | --- | | Timestamp | When the call occurred | | Call Direction | Outbound (BIC) or Inbound (UIC) | | Signaling | `SIP` or `GRAPH_API` | | Call ID | WhatsApp call identifier (`wacid.`) — **provide this when contacting Meta support** | | Request ID | Identifier for the request that initiated the call | | Call Details | Event log for the call lifecycle | Calling Error Codes[](https://dualhook.com/docs/calling-troubleshooting#calling-error-codes) --------------------------------------------------------------------------------------------- | Code | HTTP | Meaning | Fix | | --- | --- | --- | --- | | `100` | 400 | Invalid parameter (or SDP validation error — details in `error_data`) | Fix payload; check SDP RFC 8866 compliance | | `613` | 429 | Permission state API rate limit hit | Reduce frequency of `/call_permissions` polls | | `131009` | 400 | Interactive type `voice_call` not supported (only `button`, `list`) | Sender must be in supported country | | `131030` | 400 | Recipient phone not in allowed list (PTN test only) | Add recipient to allowed list | | `131044` | 400 | No valid payment method | Attach payment method to WABA | | `131055` | 400 | Method not allowed — Graph API call attempted on SIP-enabled number | Use SIP signaling instead | | `138000` | 401 | Calling not enabled | [Configure call settings](https://dualhook.com/docs/calling-configuration) | | `138001` | 400 | Receiver uncallable (not on WhatsApp / hasn't accepted ToS / unsupported client) | Confirm with recipient; check supported clients | | `138002` | 429 | Concurrent calls limit (1,000) reached | Throttle inbound capacity | | `138003` | 400 | Duplicate call — one is already ongoing with this user | Wait for current call to end | | `138004` | 500 | Connection error during call setup | Investigate connection params; retry | | `138005` | 429 | Call rate limit exceeded | Reduce sending rate | | `138006` | 401 | No approved call permission found | Get permission first ([Call Permissions](https://dualhook.com/docs/calling-permissions)
) | | `138007` | 500 | Connect timeout — business didn't apply offer/answer SDP in time | Speed up connect-API response | | `138009` | 400 | Permission request limit hit | Wait for limit reset (or for a connected call to reset it) | | `138012` | 400 | BIC daily limit hit (100 connected calls / 24h per pair) | `error_data` has timestamp for next allowed call | | `138013` | 400 | BIC not available for this business phone number country | See [availability](https://dualhook.com/docs/calling#availability) | | `138014` | 400 | Calling temporarily disabled (low quality) | Improve outreach quality, wait for restriction to expire | | `138015` | 400 | Calling cannot be enabled | Check messaging limit ≥ 2,000 | | `138017` | 400 | Permission request can't be sent — permanent permission already exists | Just place the call directly | | `138018` | 400 | Calling can't be enabled — technical prerequisites not met | Configure SIP or ensure an app is subscribed to both WABA and `calls` field | | `138019` | 400 | Call setup failed (in terminate webhook) | Retry; check media path | | `138020` | 400 | Relay connection failed (in terminate webhook) | Retry; check network | | `138021` | 400 | Media receive timeout — no media for ~20–30s (in terminate webhook) | Verify your media server is sending RTP | | `138022` | 400 | Media transmit timeout (in terminate webhook) | Investigate outbound media path | | `138023` | 400 | Call accepted but terminated with no media signals | Investigate WebRTC setup — Cloud API couldn't determine if call was connected | SIP-Specific Errors[](https://dualhook.com/docs/calling-troubleshooting#sip-specific-errors) --------------------------------------------------------------------------------------------- Status codes returned over SIP signaling, in addition to the `131055` Graph-API-attempted error above: | SIP Status | Meaning | Fix | | --- | --- | --- | | `400 Asset not found` | `From` header phone number isn't a registered business number | Check the number; resend with correct `From` | | `403 SIP server X from INVITE does not match...` | `From` header domain ≠ configured SIP server hostname | Update SIP config or `From` header (sub-domains are valid) | | `403 No Approved Call Permission Found` | No permission from recipient (or recipient not on WhatsApp) | Get permission ([Call Permissions](https://dualhook.com/docs/calling-permissions)
) | | `403 The app [APP_ID] configured for SIP server X is not authorized for phone number id [ID]` | App used to configure SIP lacks `whatsapp_business_messaging` on the number | Reconfigure with the right app | | `403 Business Initiated Connected Call Per Day Limit Hit` | 5 connected BICs in 24h reached | Adjust call rate; check pricing tier | | `403 Duplicate call / Invalid digest auth response / Business eligibility payment issue / SDP Validation Error` | Various — read the error message | Check earlier sections | | `404 Not found / SIP INVITEs using IP in request URI are not allowed` | Don't use IP literal in request URI | Use the configured SIP request URI | | `407 Proxy Authentication Required` | Standard digest auth challenge from Meta | Resend with `Authorization` header | | `408 RTP Timeout` | No media received for too long | See [Media-Related Issues](https://dualhook.com/docs/calling-troubleshooting#media-related-issues) | | `480 Temporarily Unavailable` | User unreachable / didn't answer | Try later; unanswered calls degrade permission | | `486 Busy Here` | User declined the call | Try later; rejections degrade permission | | `487 Request Terminated` | You sent SIP CANCEL before Meta responded to your INVITE | Expected — your CANCEL worked | | `503 Service Unavailable` | Generic internal error | Retry later; contact Meta support if persistent | Media-Related Issues[](https://dualhook.com/docs/calling-troubleshooting#media-related-issues) ----------------------------------------------------------------------------------------------- | Symptom | Cause | Fix | | --- | --- | --- | | Call disconnects ~20 seconds after ringing starts | No media flowing from business to user for 20s after call setup | Check media server is initiating media session and sending RTP. Check firewall for packet drops. Capture pcap for support. | | Call disconnects ~30 seconds after connection | After call connect, no media from business to user for 30s | Send at least RTCP packets even during silence (e.g. waiting for IVR input). Check why media server stops sending. | If you're hitting these, capture network traffic with `tcpdump` or `pcap` and attach it when contacting Meta support. Audio Clipping[](https://dualhook.com/docs/calling-troubleshooting#audio-clipping) ----------------------------------------------------------------------------------- A common symptom when bridging WhatsApp's WebRTC media leg to a SIP leg: the WhatsApp consumer misses ~1 second of business audio at the start of the call. If your IVR plays "1-2-3", the user hears "2-3" or just "3". **Root cause:** the SIP user-agent starts transmitting media before the WebRTC leg between you and Meta is fully ready. Meta drops the early packets. **Mitigations** (any of these works; pick what fits): | Approach | Trade-off | | --- | --- | | **Switch to SDES** for SRTP key exchange (instead of DTLS) | Eliminates the ~3-second DTLS handshake delay. See [SIP Configuration → SDES vs DTLS](https://dualhook.com/docs/calling-sip#sdes-vs-dtls-for-srtp-key-exchange) | | **Pre-accept** before sending SIP INVITE | "Optimistic accept" — establishes WebRTC connection ahead of SIP. RFC-recommended approach | | **Delayed audio playback** | Configure SIP UA to wait for ACK from your WebRTC endpoint before playing audio | | **Connection state-based delay** | Wait until WebRTC connection state is `connected` before playing audio | | **Buffer SIP media packets** | Hold packets until WebRTC is up | | **Insert silence** at start of IVR audio | Simple; gives WebRTC time to establish during silence | For full sequence-diagram detail, see Meta's "Audio clipping issue and solution" section. Most teams use SDES + pre-accept and call it done. "No Fingerprint Found in SDP"[](https://dualhook.com/docs/calling-troubleshooting#no-fingerprint-found-in-sdp) --------------------------------------------------------------------------------------------------------------- You're using DTLS for SRTP key exchange but your SDP is missing the `a=fingerprint` line. **Fix:** add the fingerprint line to your SDP. Or, if you can't, **switch to SDES** which doesn't need it. See [SIP Configuration → Configure SRTP Key Exchange](https://dualhook.com/docs/calling-sip#configure-srtp-key-exchange) . Common Symptoms — Quick Triage[](https://dualhook.com/docs/calling-troubleshooting#common-symptoms--quick-triage) ------------------------------------------------------------------------------------------------------------------ | Symptom | First thing to check | | --- | --- | | BIC fails immediately with no errors | Permission status (`/call_permissions`) — likely no approved permission | | BIC works in test, fails in production | BIC country availability (US/CA/EG/VN/NG excluded); messaging limit ≥ 2,000 | | UIC webhook never arrives | `calls` field subscription on the right app; SIP not accidentally enabled (check `/settings`) | | SIP INVITE never received from Meta | TLS cert validation (`openssl s_client` test); app permissions; firewall reachability | | Call setup hangs ~3 seconds | DTLS handshake delay — switch to SDES | | User hears no audio for first second | Audio clipping — see above | | Call drops after 20–30s | Media flow stopped or never started — check media server logs | | `403` SIP errors generally | Almost always digest auth (resend with `Authorization`) or `From` domain mismatch | | `131055` on Graph API | SIP is enabled — use SIP signaling, or disable SIP if you want Graph API back | Related ------- * [Cloud API Calling](https://dualhook.com/docs/calling) Voice calling on WhatsApp Business Cloud API: VoIP via WebRTC or SIP, business-initiated and user-initiated calls, prerequisites, availability, and integration patterns. * [Business-Initiated Calls](https://dualhook.com/docs/calling-business-initiated) Place outbound voice calls to WhatsApp users. POST /calls with action: connect, SDP offer/answer flow, call status webhooks (RINGING/ACCEPTED/REJECTED), terminate webhook with duration, biz\_opaque\_callback\_data tracking. * [User-Initiated Calls](https://dualhook.com/docs/calling-user-initiated) Receive inbound voice calls from WhatsApp users. Call Connect webhook with SDP offer, pre-accept for faster setup, accept/reject/terminate actions, DTMF dial pad, companion-device support. * [SIP Configuration](https://dualhook.com/docs/calling-sip) Use SIP signaling instead of Graph API + webhooks for WhatsApp calling. TLS certificate requirements, digest authentication, business and user initiated SIP flows, SDES vs DTLS for SRTP, custom SIP headers. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # Calling Configuration | Dualhook Browse Docs Calling Configuration ===================== Enable calling on a business phone number, configure call hours and holidays, restrict the call icon by country, set callback permissions, choose audio codecs, listen for settings webhooks. What This Page Covers[](https://dualhook.com/docs/calling-configuration#what-this-page-covers) ----------------------------------------------------------------------------------------------- How to enable and configure the WhatsApp Business Calling API on a business phone number — including who can call, when calls are accepted, audio codec choices, and how to listen for settings changes via webhook. If you want to use SIP signaling instead of Graph API + webhooks, the SIP-specific portions of `//settings` live on [SIP Configuration](https://dualhook.com/docs/calling-sip) . The connection-scoped [`dh_live_` Runtime API](https://dualhook.com/docs/runtime-api-reference#phone-settings-and-calling) supports the safe calling configuration on this page. It deliberately rejects `include_sip_credentials`, SIP/SRTP material, payload encryption, storage configuration, and live `/calls` signaling. This Runtime API calling support applies to Cloud API connections. Meta does not expose Calling API settings for Coexistence numbers; voice and video remain in the WhatsApp Business app, and Dualhook returns `409` for Coexistence calling updates. Configure Call Settings[](https://dualhook.com/docs/calling-configuration#configure-call-settings) --------------------------------------------------------------------------------------------------- POST //settings Permission: `whatsapp_business_management` (advanced access for end-business clients). Full request body shape: { "calling": { "status": "ENABLED", "call_icon_visibility": "DEFAULT", "call_icons": { "restrict_to_user_countries": ["US", "BR"] }, "call_hours": { "status": "ENABLED", "timezone_id": "America/Manaus", "weekly_operating_hours": [\ { "day_of_week": "MONDAY", "open_time": "0400", "close_time": "1020" },\ { "day_of_week": "TUESDAY", "open_time": "0900", "close_time": "1700" }\ ], "holiday_schedule": [\ { "date": "2026-01-01", "start_time": "0000", "end_time": "2359" }\ ] }, "callback_permission_status": "ENABLED", "audio": { "additional_codecs": ["PCMA", "PCMU"] } } } All fields are optional. Omitting a field leaves its current value unchanged — except `call_hours.holiday_schedule`, which is **replaced wholesale** by what you send (omit it and any prior holidays are deleted). Success response: { "success": true } calling.status[](https://dualhook.com/docs/calling-configuration#callingstatus) -------------------------------------------------------------------------------- | Value | Effect | | --- | --- | | `ENABLED` | Calling features active. WhatsApp client renders the call-button icon in chat and business-info screens. | | `DISABLED` | Calling features off. Call icon hidden across the app. | Updates propagate near-real-time to users who already have your number in their contacts; for everyone else, propagation is eventual but server-side semantics are honored immediately. call\_icon\_visibility[](https://dualhook.com/docs/calling-configuration#call_icon_visibility) ----------------------------------------------------------------------------------------------- Controls whether the call button is shown to users when calling is `ENABLED`: | Value | What it does | | --- | --- | | `DEFAULT` | Call button visible in chat menu bar and business info page. Users can place unsolicited calls. | | `DISABLE_ALL` | Call button hidden in chat and info page. All external entry points (contact-app dial, etc.) also disabled. **But** users can still call you via [Call Buttons & Deep Links](https://dualhook.com/docs/calling-call-buttons)
— `voice_call` interactive messages, template buttons, `wa.me/call/` links. | `DISABLE_ALL` is best treated as a coarse first-level filter; rely on webhooks + per-message routing to enforce per-user policy. call\_icons (Restrict by Country)[](https://dualhook.com/docs/calling-configuration#call_icons-restrict-by-country) -------------------------------------------------------------------------------------------------------------------- Restrict where the call button appears by user country. Empty array means no restriction. "call_icons": { "restrict_to_user_countries": ["US", "BR"] } Country is determined by the user's registered phone number, not their physical location. A user with a US-registered number traveling abroad still sees the icon if `US` is in the list. call\_hours[](https://dualhook.com/docs/calling-configuration#call_hours) -------------------------------------------------------------------------- Restrict when user-initiated calls are accepted. Outside the configured weekly hours and holiday schedule, callers see a "chat instead" / "request callback" screen (callback option only shown if `callback_permission_status: ENABLED`). "call_hours": { "status": "ENABLED", "timezone_id": "America/Manaus", "weekly_operating_hours": [\ { "day_of_week": "MONDAY", "open_time": "0400", "close_time": "1020" }\ ], "holiday_schedule": [\ { "date": "2026-01-01", "start_time": "0000", "end_time": "2359" }\ ] } | Field | Notes | | --- | --- | | `status` | `ENABLED` enforces the schedule. `DISABLED` makes the business 24/7. | | `timezone_id` | IANA timezone name (`America/Menominee`, `Asia/Singapore`, etc.). | | `weekly_operating_hours[]` | Up to **2** entries per `day_of_week`. Times are 4-digit 24-hour strings (e.g. `"0400"` = 04:00, `"1020"` = 10:20). `open_time` must precede `close_time`. No overlapping entries. | | `holiday_schedule[]` | Optional. Up to **20** date overrides. Same time format. **Sending the field replaces all existing holidays** — to keep prior holidays, include them in every request. | callback\_permission\_status[](https://dualhook.com/docs/calling-configuration#callback_permission_status) ----------------------------------------------------------------------------------------------------------- When `ENABLED`, after a user calls you (whether you pick up or miss it), they're auto-prompted to grant call permission so you can call them back. See [Call Permissions](https://dualhook.com/docs/calling-permissions) for the full mechanics. audio.additional\_codecs[](https://dualhook.com/docs/calling-configuration#audioadditional_codecs) --------------------------------------------------------------------------------------------------- OPUS is always the default and cannot be removed. Optionally enable G.711 (PCMA/PCMU) for interoperability with legacy telephony / PSTN gateways. "audio": { "additional_codecs": ["PCMA", "PCMU"] } When G.711 is enabled and selected during SDP negotiation, audio is **transcoded** between OPUS (WhatsApp side) and G.711 (your side) — adds latency, lower fidelity, higher bandwidth (~64 kbps vs OPUS's ~20 kbps for similar quality). Use only when you must interop with G.711-only equipment. To disable additional codecs: "audio": { "additional_codecs": [] } Get Current Settings[](https://dualhook.com/docs/calling-configuration#get-current-settings) --------------------------------------------------------------------------------------------- GET //settings Meta's direct Graph API can return additional phone settings. Through Dualhook, the response is projected to safe `calling` and `user_identity_change` fields. Do not pass `include_sip_credentials`; the Runtime API rejects it and never returns SIP passwords, server configuration, or encryption/storage material. { "calling": { "status": "ENABLED", "call_icon_visibility": "DEFAULT", "callback_permission_status": "ENABLED", "call_hours": { /* ... */ }, "audio": { "additional_codecs": ["PCMA", "PCMU"] } } } Restrictions in the Response[](https://dualhook.com/docs/calling-configuration#restrictions-in-the-response) ------------------------------------------------------------------------------------------------------------- If Meta has temporarily restricted your phone number's calling capability (typically due to high negative user feedback), the response includes a `restrictions` object: "calling": { "restrictions": { "restrictions_list": [\ {\ "type": "RESTRICTED_BUSINESS_INITIATED_CALLING",\ "reason": "Business initiated calling capability has been temporarily disabled for this phone number due to high negative feedback from users.",\ "expiration": 1754072386\ }\ ] } } | Field | Notes | | --- | --- | | `type` | `RESTRICTED_BUSINESS_INITIATED_CALLING` or `RESTRICTED_USER_INITIATED_CALLING` | | `reason` | Human-readable description | | `expiration` | Unix timestamp (UTC) when the restriction lifts | When restricted, calling endpoints for the affected direction return errors until expiration. Reduce sender load on permissions and outbound calls and improve quality before the next restriction hits. Settings Webhook (account\_settings\_update)[](https://dualhook.com/docs/calling-configuration#settings-webhook-account_settings_update) ----------------------------------------------------------------------------------------------------------------------------------------- Subscribe to the `account_settings_update` webhook field to be notified when phone-number settings change — including your own updates. Currently only changes to these calling fields trigger the webhook: `status`, `call_icon_visibility`, `callback_permission_status`, `sip.status`, and `srtp_key_exchange_protocol`. { "object": "whatsapp_business_account", "entry": [\ {\ "id": "102290129340398",\ "changes": [\ {\ "field": "account_settings_update",\ "value": {\ "messaging_product": "whatsapp",\ "timestamp": "1671644824",\ "type": "phone_number_settings",\ "phone_number_settings": {\ "phone_number_id": "123456789012345",\ "calling": {\ "status": "ENABLED",\ "call_icon_visibility": "DEFAULT",\ "callback_permission_status": "ENABLED",\ "call_hours": { /* full call_hours object */ },\ "sip": {\ "status": "ENABLED",\ "servers": [{ "hostname": "[REDACTED]", "port": 5061 }]\ }\ }\ }\ }\ }\ ]\ }\ ] } Subscribed app must have `whatsapp_business_management` permission. Same app rule as messaging webhooks: it must be subscribed to the WABA. **Dualhook stance:** Dualhook does **not** auto-subscribe to `account_settings_update`. To receive these webhooks, subscribe your own app to the field at the WABA level and host the receiver yourself — these events bypass Dualhook entirely (see [Webhook Subscription Fields](https://dualhook.com/docs/webhooks) ). Client Refresh Behavior[](https://dualhook.com/docs/calling-configuration#client-refresh-behavior) --------------------------------------------------------------------------------------------------- After updating call settings, WhatsApp users may take **up to 7 days** to reflect the change in their client UI — though most refresh much sooner. The server-side semantics are honored immediately; the lag is purely cosmetic on the user's app. To force an immediate refresh in WhatsApp: enter the chat with the business and open the chat info page. That triggers a fresh fetch. You can also configure call settings from **WhatsApp Manager → Account tools → Phone numbers → gear icon → Calls tab**. Related ------- * [Cloud API Calling](https://dualhook.com/docs/calling) Voice calling on WhatsApp Business Cloud API: VoIP via WebRTC or SIP, business-initiated and user-initiated calls, prerequisites, availability, and integration patterns. * [SIP Configuration](https://dualhook.com/docs/calling-sip) Use SIP signaling instead of Graph API + webhooks for WhatsApp calling. TLS certificate requirements, digest authentication, business and user initiated SIP flows, SDES vs DTLS for SRTP, custom SIP headers. * [Business-Initiated Calls](https://dualhook.com/docs/calling-business-initiated) Place outbound voice calls to WhatsApp users. POST /calls with action: connect, SDP offer/answer flow, call status webhooks (RINGING/ACCEPTED/REJECTED), terminate webhook with duration, biz\_opaque\_callback\_data tracking. * [User-Initiated Calls](https://dualhook.com/docs/calling-user-initiated) Receive inbound voice calls from WhatsApp users. Call Connect webhook with SDP offer, pre-accept for faster setup, accept/reject/terminate actions, DTMF dial pad, companion-device support. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Call Permissions: Temporary, Permanent, and Callback | Dualhook Browse Docs Call Permissions ================ Obtain permission to call WhatsApp users: temporary (7-day) and permanent permissions, three ways to obtain (free-form request, template request, callback auto-prompt, business profile), get permission state endpoint, permission reply webhook. Overview[](https://dualhook.com/docs/calling-permissions#overview) ------------------------------------------------------------------- Before your business can place a [business-initiated call](https://dualhook.com/docs/calling-business-initiated) to a WhatsApp user, the user must grant call permission. The user is in full control — they can accept (temporary or permanent), decline, or ignore. They can revoke a granted permission at any time. Your business cannot revoke or modify a granted permission. As of **November 3, 2025**, permanent permissions are available — users can grant ongoing permission to call. Permanent permission data is stored until the user revokes it. Permanent permissions are only available in regions where business-initiated calling is available. Temporary vs Permanent[](https://dualhook.com/docs/calling-permissions#temporary-vs-permanent) ----------------------------------------------------------------------------------------------- | | Temporary | Permanent | | --- | --- | --- | | Duration | 7 calendar days (168 hours) from approval | No expiration | | Connected calls cap | 100 / 24h per business + user pair | 100 / 24h per business + user pair | | Auto-revocation on unanswered | Yes (after 4 consecutive unanswered) | Yes (after 4 consecutive unanswered) | | User revocable any time | Yes | Yes | Both kinds share the same per-day connected-call limit. The difference is just the expiration window. Three Ways to Obtain Permission[](https://dualhook.com/docs/calling-permissions#three-ways-to-obtain-permission) ----------------------------------------------------------------------------------------------------------------- | Method | When to use | | --- | --- | | **1\. Send a call permission request message** | Proactively, when you want to call a user out of the blue (within an open CSW: free-form, otherwise: template) | | **2\. Enable `callback_permission_status`** | Automatically prompt for permission when a user calls you (whether you answer or miss) | | **3\. User grants permanent permission via business profile** | User-driven — they navigate to your business profile and toggle on calling permission | Request Limits[](https://dualhook.com/docs/calling-permissions#request-limits) ------------------------------------------------------------------------------- Per business + WhatsApp user pair (these are designed to protect users from spam): | Limit | Production | Sandbox / Test | | --- | --- | --- | | Permission requests per day | 1 | 25 | | Permission requests per 7 days | 2 | 100 | Limits **reset** when any connected call (BIC or UIC) happens between you and that user. They apply to both free-form and template permission requests combined. Sending these messages also costs you — call permission request messages are subject to standard messaging charges. Free-Form Call Permission Request[](https://dualhook.com/docs/calling-permissions#free-form-call-permission-request) --------------------------------------------------------------------------------------------------------------------- Use during an open CSW. Header and footer are not supported on free-form variants. POST //messages { "messaging_product": "whatsapp", "recipient_type": "individual", "to": "12015550123", "type": "interactive", "interactive": { "type": "call_permission_request", "action": { "name": "call_permission_request" }, "body": { "text": "We would like to call you to help support your query on Order No: ON-12853." } } } `body` is technically optional but strongly recommended — without context, users decline. The `call_permission_request` interactive object itself cannot be customized; only the body text is yours. Note: sending this to users on older app versions returns error `131026`. Template Call Permission Request[](https://dualhook.com/docs/calling-permissions#template-call-permission-request) ------------------------------------------------------------------------------------------------------------------- Use outside an open CSW (or whenever you'd prefer a templated approach). Templates support header, body, and footer. Body is required. **Create the template:** POST //message_templates { "name": "sample_cpr_template", "language": "en", "category": "MARKETING", "components": [\ {\ "type": "HEADER",\ "text": "Support of Order No: {{1}}",\ "example": { "body_text": [["ON-12345"]] }\ },\ {\ "type": "BODY",\ "text": "We would like to call you to help support your query on Order No: {{1}} for the item {{2}}.",\ "example": { "body_text": [["ON-12345", "Avocados"]] }\ },\ { "type": "FOOTER", "text": "Talk to you soon!" },\ { "type": "call_permission_request" }\ ] } Category must be `MARKETING` or `UTILITY`. The `call_permission_request` component cannot be combined with other interactive components. **Send the template:** POST //messages { "messaging_product": "whatsapp", "to": "12015550123", "type": "template", "template": { "name": "sample_cpr_template", "language": { "code": "en" }, "components": [\ {\ "type": "body",\ "parameters": [\ { "type": "text", "text": "John Smith" },\ { "type": "text", "text": "order #1522" }\ ]\ }\ ] } } Template-based call permission requests are also covered as a category-specialty template in [Marketing Messages](https://dualhook.com/docs/marketing-messages#specialty-marketing-templates) and [Utility Messages](https://dualhook.com/docs/utility-messages#call-permission-request-utility) . Callback Permission (User Calls You)[](https://dualhook.com/docs/calling-permissions#callback-permission-user-calls-you) ------------------------------------------------------------------------------------------------------------------------- When `callback_permission_status: ENABLED` on the phone number's call settings, the WhatsApp client automatically prompts the user with a call permission request after they call you (whether you picked up or missed it). The permission flows back via the [permission reply webhook](https://dualhook.com/docs/calling-permissions#user-call-permission-reply-webhook) with `response_source: automatic`. This is the lowest-friction way to obtain permission for businesses that primarily receive calls and want to follow up. Permanent Permission from Business Profile[](https://dualhook.com/docs/calling-permissions#permanent-permission-from-business-profile) --------------------------------------------------------------------------------------------------------------------------------------- Users can grant permanent calling permission to a business at any time through the business's WhatsApp profile. This is user-driven — there's no API to nudge them toward this UI. When granted this way, you receive a permission reply webhook with no `context.id` (since it wasn't in response to a request) and `is_permanent: true`. Get Call Permission State[](https://dualhook.com/docs/calling-permissions#get-call-permission-state) ----------------------------------------------------------------------------------------------------- GET //call_permissions?user_wa_id= Returns the current permission state plus the rate-limit budget for sending another permission request and placing a new call to that user. { "messaging_product": "whatsapp", "permission": { "status": "temporary", "expiration_time": 1745343479 }, "actions": [\ {\ "action_name": "send_call_permission_request",\ "can_perform_action": true,\ "limits": [\ { "time_period": "PT24H", "max_allowed": 1, "current_usage": 0 },\ { "time_period": "P7D", "max_allowed": 2, "current_usage": 1 }\ ]\ },\ {\ "action_name": "start_call",\ "can_perform_action": false,\ "limits": [\ {\ "time_period": "PT24H",\ "max_allowed": 5,\ "current_usage": 5,\ "limit_expiration_time": 1745622600\ }\ ]\ }\ ] } | Field | Notes | | --- | --- | | `permission.status` | `no_permission`, `temporary`, or `permanent` | | `permission.expiration_time` | Unix timestamp when temporary permission expires. Absent when `permanent`. | | `actions[].action_name` | `send_call_permission_request` (sending another request) or `start_call` (placing a new call) | | `actions[].can_perform_action` | Whether the action is currently allowed by all applicable limits | | `limits[].time_period` | ISO 8601 duration: `PT24H` = 24 hours, `P7D` = 7 days | | `limits[].limit_expiration_time` | Unix timestamp when the limit resets. Only present if `current_usage` has hit `max_allowed`. | Rate limit on this endpoint itself: **100 requests per second**. If the user phone number is uncallable for any reason (not on WhatsApp, etc.), the response returns `no_permission`. User Call Permission Reply Webhook[](https://dualhook.com/docs/calling-permissions#user-call-permission-reply-webhook) ----------------------------------------------------------------------------------------------------------------------- Fires whenever the user accepts, rejects, or has their permission auto-revoked. Includes whether the response was permanent and the source. { "messages": [\ {\ "from": "12015550123",\ "id": "wamid.sH0kFlaCGg0xcvZbgmg90lHrg2dL",\ "timestamp": "1767168000",\ "context": {\ "from": "12015550123",\ "id": "wamid.gBGGFlaCmZ9plHrf2Mh-o"\ },\ "interactive": {\ "type": "call_permission_reply",\ "call_permission_reply": {\ "response": "accept",\ "is_permanent": false,\ "expiration_timestamp": "1768550400",\ "response_source": "user_action"\ }\ }\ }\ ] } | Field | Values | Notes | | --- | --- | --- | | `response` | `accept` or `reject` | | | `is_permanent` | `true` or `false` | Always `false` for temporary; `true` for permanent. Absent on rejects. | | `expiration_timestamp` | Unix timestamp | Present only for accepts of temporary permissions | | `response_source` | `user_action` or `automatic` | `automatic` = callback permission auto-granted on user-initiated call, or auto-revoked after consecutive unanswered calls | | `context.id` | `wamid.` of permission request, or `wacid.` of missed call, or absent | Tells you what triggered the response. Absent when user grants from business profile. | **No webhook fires** when a temporary permission expires naturally — use the `expiration_timestamp` you received on the accept webhook, or query the [permission state endpoint](https://dualhook.com/docs/calling-permissions#get-call-permission-state) . Common scenarios: | Scenario | `response` | `is_permanent` | `response_source` | `context.id` | | --- | --- | --- | --- | --- | | User accepts temporary from request message | `accept` | `false` | `user_action` | `wamid.` of request | | User accepts permanent from request message | `accept` | `true` | `user_action` | `wamid.` of request | | User accepts permanent from business profile | `accept` | `true` | `user_action` | absent | | User rejects from request message | `reject` | absent | `user_action` | `wamid.` of request | | Auto-permission via callback (user called you) | `accept` | `false` | `automatic` | `wacid.` of missed call | | Auto-revoked after 4 unanswered BIC | `reject` | absent | `automatic` | absent | Automatic Revocation (Unanswered Calls)[](https://dualhook.com/docs/calling-permissions#automatic-revocation-unanswered-calls) ------------------------------------------------------------------------------------------------------------------------------- | Consecutive unanswered BIC calls | Effect | | --- | --- | | 2 | System message in WhatsApp asks the user to reconsider their permission | | 4 | Permission **auto-revoked**. You receive a permission reply webhook with `response: reject`, `response_source: automatic` | After auto-revoke, you'd need to send a new permission request to call again. Sandbox accounts have relaxed thresholds (5 / 10 instead of 2 / 4). Permission Request Expiration[](https://dualhook.com/docs/calling-permissions#permission-request-expiration) ------------------------------------------------------------------------------------------------------------- A pending request (sent but not yet responded to) expires when **any** of these happen: * The user interacts with a **subsequent** new permission request from you (the prior one auto-expires). * 7 days after the user accepts or declines (i.e. the granted permission expires, not the request). * 7 days after delivery if the user never responds. Related ------- * [Business-Initiated Calls](https://dualhook.com/docs/calling-business-initiated) Place outbound voice calls to WhatsApp users. POST /calls with action: connect, SDP offer/answer flow, call status webhooks (RINGING/ACCEPTED/REJECTED), terminate webhook with duration, biz\_opaque\_callback\_data tracking. * [Marketing Messages](https://dualhook.com/docs/marketing-messages) Marketing template messaging through Dualhook's standard Cloud API runtime, plus boundaries for Meta's separate MM API product. * [Utility Messages](https://dualhook.com/docs/utility-messages) Utility template messaging hub: components and buttons, specialty types, the October 2026 CSW billing change, volume tiers, content rules, and recategorization. * [Calling Troubleshooting](https://dualhook.com/docs/calling-troubleshooting) WhatsApp Calling errors and fixes: full error code table (138000-138023), SIP-specific status codes, media-flow timeouts, audio clipping causes and mitigations (SDES, pre-accept), quick triage guide. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) --- # WhatsApp Cloud API Error Codes — Full Meta API Reference & Fixes | Dualhook Browse Docs Meta API Error Reference ======================== Complete WhatsApp Cloud API and Meta Graph error code reference — what each code means, where it appears, and how to fix it. Meta Graph and WhatsApp Cloud API errors can appear in two places: * The immediate API response when your backend calls Meta. * A later `statuses[].errors[]` object in a message-status webhook (asynchronous). Some errors arrive through both channels. Dualhook relays outbound mutations without automatic retries. Safe Meta status, code, message, and trace fields remain available, while credential-like fields and values are recursively sanitized. Management successes are projected through endpoint-specific field allowlists, and raw Graph pagination URLs are removed. Inbound message-status webhooks still arrive directly at the endpoint configured with [Webhook Override](https://dualhook.com/docs/webhook-override) . Runtime-owned authorization, scope, validation, content-type, and rate-limit errors are documented in the [Runtime API Endpoint Reference](https://dualhook.com/docs/runtime-api-reference#errors) and troubleshot by symptom in [Runtime API Errors](https://dualhook.com/docs/runtime-api-errors) . Build Meta-specific handling around the numeric `code` and `error_data.details`, not titles or HTTP status. Where Errors Appear[](https://dualhook.com/docs/api-errors#where-errors-appear) -------------------------------------------------------------------------------- ### Immediate API response[](https://dualhook.com/docs/api-errors#immediate-api-response) If Meta rejects a request before accepting it, your sender receives an HTTP error and an `error` object immediately. Examples: invalid token, missing parameter, wrong template variable count, permission issue. ### Status webhook after the send[](https://dualhook.com/docs/api-errors#status-webhook-after-the-send) If Meta accepts the request but later cannot deliver the message, your webhook receives a `failed` status with `statuses[].errors[]`. Cloud API surfaces asynchronous errors under `entry.changes.value.errors` and `entry.changes.value.messages.errors`. Examples: recipient cannot receive the message, pair rate limit, user stopped marketing messages. Error Object Format[](https://dualhook.com/docs/api-errors#error-object-format) -------------------------------------------------------------------------------- Most API failures return an `error` object with this shape: { "error": { "message": "(#130429) Rate limit hit", "type": "OAuthException", "code": 130429, "error_data": { "messaging_product": "whatsapp", "details": "Cloud API message throughput has been reached." }, "error_subcode": 2494055, "fbtrace_id": "Az8or2yhqkZfEZ-_4Qn_Bam" } } Use `error.code` and `error_data.details` for handling, and retain `fbtrace_id` for support correlation. If you log Meta's message or details in your own system, redact phone numbers, message content, credentials, and other customer data first. `error_subcode` is deprecated and is not returned on v16.0+, so do not build logic around it. Status webhooks use a similar shape inside `statuses[].errors[]`: { "statuses": [\ {\ "id": "wamid...",\ "status": "failed",\ "errors": [\ {\ "code": 131026,\ "title": "Message undeliverable",\ "message": "Message undeliverable",\ "error_data": {\ "details": "Message Undeliverable."\ }\ }\ ]\ }\ ] } Quick Triage[](https://dualhook.com/docs/api-errors#quick-triage) ------------------------------------------------------------------ | Symptom | First place to check | | --- | --- | | Sends fail immediately with `190`, `0`, `10`, or `200` | Token validity, app permissions, WABA/phone-number asset tasks | | Free-form text fails with `131047` (legacy `470`) | Customer service window expired; send a template instead | | Template send fails with `132000` / `132001` | Template name, language, status, and variable count | | Status webhook shows `131026` | Recipient cannot receive the message; validate opt-in/contact quality | | Status webhook shows `131056` | Too many messages to the same user too quickly; slow down per recipient | | Throughput error `130429` or `131048` / `131064` | Per-number throughput, quality, or template-classification limits | | Marketing send shows `131049` / `131050` / `131063` | Marketing eligibility, recipient preference, send endpoint, or region | | Status webhook shows `137000` | Identity-key check mismatch; re-verify the user outside WhatsApp | | Account-wide failures `368` / `131031` / `130497` | Account restriction, policy enforcement, or country eligibility | Authorization and Permission Errors[](https://dualhook.com/docs/api-errors#authorization-and-permission-errors) ---------------------------------------------------------------------------------------------------------------- | Code | Meaning | Recommended action | | --- | --- | --- | | `0` | Unable to authenticate the app user | Token expired, invalidated, or access revoked — get a new access token | | `3` | Capability or permissions issue | Verify the endpoint's required permissions with the access token debugger | | [`10`](https://dualhook.com/docs/whatsapp-error-10) | Permission not granted or removed | Verify app permissions and review status; check API eligibility requirements | | [`190`](https://dualhook.com/docs/whatsapp-error-190) | Meta authorization expired or invalidated | Reauthorize the connection through Embedded Signup; Meta tokens are never revealed | | [`200`](https://dualhook.com/docs/whatsapp-error-200) | Authorization or permission failure | For runtime sends, use a valid connection `dh_live_...` key. For dashboard management operations, reauthorize if required permissions were removed | | `131005` | Permission not granted or removed | Confirm the token belongs to the business/phone number and holds the needed WhatsApp permissions | Integrity and Policy Errors[](https://dualhook.com/docs/api-errors#integrity-and-policy-errors) ------------------------------------------------------------------------------------------------ | Code | Meaning | Recommended action | | --- | --- | --- | | [`368`](https://dualhook.com/docs/whatsapp-error-368) | WABA restricted or disabled for a platform policy violation | See Meta's Policy Enforcement docs; check [Account Health](https://dualhook.com/docs/account-health) | | [`130497`](https://dualhook.com/docs/whatsapp-error-130497) | Business restricted from messaging users in certain countries | Review the WhatsApp Business Messaging Policy for allowed countries in your category | | [`131031`](https://dualhook.com/docs/whatsapp-error-131031) | Account locked/restricted, or request data could not be verified (e.g. wrong two-step PIN) | Review account quality/policy status; the [Health Status API](https://dualhook.com/docs/health-monitoring)
may show the lock reason | Request and Parameter Errors[](https://dualhook.com/docs/api-errors#request-and-parameter-errors) -------------------------------------------------------------------------------------------------- | Code | Meaning | Recommended action | | --- | --- | --- | | [`100`](https://dualhook.com/docs/whatsapp-error-100) | Unsupported or misspelled parameter | Check the endpoint reference for supported parameter names, formats, and length limits | | [`131008`](https://dualhook.com/docs/whatsapp-error-131008) | Required parameter missing | Add the missing field per the endpoint reference | | [`131009`](https://dualhook.com/docs/whatsapp-error-131009) | One or more parameter values invalid | Check field formats, IDs, button payloads, or reaction target message | | [`131051`](https://dualhook.com/docs/whatsapp-error-131051) | Unsupported message type | Fix the message `type` and object structure | | `131062` | Message type does not support BSUID recipients | Use a phone number for one-tap/zero-tap/copy-code auth templates — see [BSUID Transition](https://dualhook.com/docs/contacts-bsuid) | | `135000` | Generic error with request parameters | Verify endpoint syntax; contact support if it persists | Delivery Errors[](https://dualhook.com/docs/api-errors#delivery-errors) ------------------------------------------------------------------------ | Code | Meaning | Recommended action | | --- | --- | --- | | [`130403`](https://dualhook.com/docs/whatsapp-error-130403) | Business has blocked the end user on WhatsApp | Do not retry; unblock the user to resume sending | | `130472` | Message not sent as part of an experiment | Expected for Marketing Message experiments — no action needed | | `131021` | Sender and recipient phone number are the same | Send to a number different from the sender | | [`131026`](https://dualhook.com/docs/whatsapp-error-131026) | Message undeliverable | Recipient is not on WhatsApp, hasn't accepted ToS, or is on an outdated app version — validate the destination and opt-in | | [`131047`](https://dualhook.com/docs/whatsapp-error-131047) | More than 24h since the recipient last replied (legacy code `470`) | Send an approved template message to re-engage | | [`131049`](https://dualhook.com/docs/whatsapp-error-131049) | Not delivered to maintain healthy ecosystem engagement (per-user marketing limit) | Wait ≥24h before resending; segment recipients — see Per-User Marketing Template Limits | | [`131050`](https://dualhook.com/docs/whatsapp-error-131050) | Recipient stopped marketing messages from your business | Suppress marketing sends; subscribe to the `user_preferences` webhook to track opt-out/resume | | `131052` | Unable to download media sent by the user | Check `error_data.details`; ask the user to resend via a non-WhatsApp method | | `131053` | Unable to upload the media used in the message | Confirm the media MIME type is supported before retrying | Throttling and Rate-Limit Errors[](https://dualhook.com/docs/api-errors#throttling-and-rate-limit-errors) ---------------------------------------------------------------------------------------------------------- | Code | Meaning | Recommended action | | --- | --- | --- | | [`4`](https://dualhook.com/docs/whatsapp-error-4) | App reached its API call rate limit | Back off and retry later; reduce call frequency | | [`80007`](https://dualhook.com/docs/whatsapp-error-80007) | WABA reached its rate limit | Reduce query frequency; see [Capacity & Limits](https://dualhook.com/docs/capacity-quality-limits) | | [`130429`](https://dualhook.com/docs/whatsapp-error-130429) | Cloud API message throughput reached (per-number ~80 msg/s default) | Queue/throttle and retry with backoff — see [Throughput](https://dualhook.com/docs/capacity-quality-limits) | | [`131048`](https://dualhook.com/docs/whatsapp-error-131048) | Spam-rate restriction (too many prior messages blocked or flagged) | Check quality status in WhatsApp Manager; improve template/account quality | | [`131056`](https://dualhook.com/docs/whatsapp-error-131056) | Pair rate limit — too many messages to the same recipient too quickly | Back off (`4^X` seconds) per retry to that recipient; see [Pair Rate Limit](https://dualhook.com/docs/capacity-quality-limits#pair-rate-limit-per-recipient) | | [`131064`](https://dualhook.com/docs/whatsapp-error-131064) | Messaging limit reached due to template classification violations | Re-check template categories; restriction lifts after the enforcement period | | [`133016`](https://dualhook.com/docs/whatsapp-error-133016) | Too many registration/deregistration attempts for this number | Wait until the number is unblocked before retrying | Template Creation Errors[](https://dualhook.com/docs/api-errors#template-creation-errors) ------------------------------------------------------------------------------------------ | Code | Meaning | Recommended action | | --- | --- | --- | | `2388040` | A template field exceeded its maximum character limit | Shorten the affected field per the error message | | `2388047` | Message header format incorrect | Fix header formatting per the error message | | `2388072` | Message body format incorrect | Fix body formatting per the error message | | `2388073` | Message footer format incorrect | Fix footer formatting per the error message | | `2388293` | Parameter-to-words ratio exceeds the limit | Reduce variables or increase message length | | `2388299` | Leading or trailing parameters not allowed | Move variables away from the start/end of the template | | `2388019` | Message template limit exceeded | A WABA allows up to 250 templates — delete unused ones. See [Templates](https://dualhook.com/docs/templates) | Template Send Errors[](https://dualhook.com/docs/api-errors#template-send-errors) ---------------------------------------------------------------------------------- | Code | Meaning | Recommended action | | --- | --- | --- | | [`132000`](https://dualhook.com/docs/whatsapp-error-132000) | Variable count mismatch vs. the approved template | Match the number of variables to the template definition | | [`132001`](https://dualhook.com/docs/whatsapp-error-132001) | Template not found in the specified language, or not approved | Use the exact name + language locale and confirm it is approved | | `132005` | Translated text too long | Shorten variable values or template text | | `132007` | Template content violates a WhatsApp policy | See the Template Review docs for the violation reason | | [`132012`](https://dualhook.com/docs/whatsapp-error-132012) | Variable parameter values formatted incorrectly | Match the component/button/media parameter format defined in the template | | `132015` | Template paused due to low quality | Improve quality and wait for re-approval, or use another template | | `132016` | Template permanently disabled (paused too many times) | Create a new template with different content | | `131061` | Template carries a `bid_spec` / max-price and can't be sent via Cloud API `/messages` | Send through the [Marketing Messages API](https://dualhook.com/docs/marketing-max-price) | | `131063` | Marketing templates disabled for this WABA's Cloud API config | Use the [Marketing Messages API](https://dualhook.com/docs/marketing-messages-api)
, or set `disable_marketing_messages_on_cloud_api` to `false` | Marketing Message Errors[](https://dualhook.com/docs/api-errors#marketing-message-errors) ------------------------------------------------------------------------------------------ The [Marketing Messages API for WhatsApp](https://dualhook.com/docs/marketing-messages-api) (MM Lite) reuses Cloud API codes with these additions: | Code | HTTP | Meaning | Recommended action | | --- | --- | --- | --- | | `100` | 400 | "Message must be a template message" | Send a marketing **template**, not a free-form message | | `131009` | 400 | Parameter value not valid | Verify parameters; ad syncing may be incomplete — wait 10 min and retry | | `131055` | 400 | Method not allowed — only marketing templates supported | Don't send authentication/utility templates on this API | | `132018` | 400 | Template validation error | Review and fix the template parameters, then resend | | `134100` | 400 | Only marketing messages supported | Send a `MARKETING`\-category template (Graph API v23.0+) | | `134101` | 400 | Template still syncing | New templates take up to 10 min to sync — wait and retry | | `134102` | 500 | Template unavailable for use | Check eligibility; confirm `marketing_messages_lite_api_status` is `ONBOARDED`, else contact support | | `1752041` | 400 | Duplicate onboarding request | The client was already invited by a partner — no action needed; all eligible WABAs onboard automatically | Flows Errors[](https://dualhook.com/docs/api-errors#flows-errors) ------------------------------------------------------------------ | Code | Meaning | Recommended action | | --- | --- | --- | | `132068` | Flow is in a blocked state | Correct the Flow before sending | | `132069` | Flow is throttled (10 messages with this Flow already sent in the last hour) | Slow down sends using this Flow | Phone Number and Registration Errors[](https://dualhook.com/docs/api-errors#phone-number-and-registration-errors) ------------------------------------------------------------------------------------------------------------------ | Code | Meaning | Recommended action | | --- | --- | --- | | `33` | Business phone number has been deleted | Verify the phone number ID is correct | | `131037` | The 555 test number has no approved display name | Set/approve a display name for the number — see [Display Names](https://dualhook.com/docs/display-names) | | `131045` | Message failed due to a phone number registration error | Register the phone number before sending | | `131057` | Business Account in maintenance mode | Often a throughput upgrade — retry shortly | | `133000` | Previous deregistration attempt failed | Deregister the number again before registering | | `133005` | Two-step verification PIN incorrect | Verify the PIN; reset it via two-step verification if needed | | `133006` | Phone number needs verification before registering | Verify and register the number | | `133008` | Too many two-step PIN guesses | Wait the time specified in `details` before retrying | | `133009` | Two-step PIN entered too quickly | Check `details` and slow down retries | | `133010` | Phone number not registered on the platform | Register the number before sending | | `133015` | Number recently deleted; deletion not yet complete | Wait ~5 minutes, then retry | | `2494100` | Account is in maintenance mode | Try again in a few minutes | Payment and Billing Errors[](https://dualhook.com/docs/api-errors#payment-and-billing-errors) ---------------------------------------------------------------------------------------------- | Code | Meaning | Recommended action | | --- | --- | --- | | [`131042`](https://dualhook.com/docs/whatsapp-error-131042) | Payment-method error (no/over-limit credit line, inactive account, missing currency/timezone, pending "Messaging For") | Verify billing setup for the WABA/business portfolio | | `131044` | No valid payment method (also seen on user-initiated [Calling](https://dualhook.com/docs/calling-troubleshooting)
) | Attach a valid payment method to the WABA | | `134011` | WhatsApp Payments terms of service acceptance pending | Accept the WhatsApp Payments ToS via the link in the error message | Phone Migration Errors[](https://dualhook.com/docs/api-errors#phone-migration-errors) -------------------------------------------------------------------------------------- | Code | Meaning | Recommended action | | --- | --- | --- | | `2388012` | Phone number already exists in your WhatsApp account | Migrate a number not already present | | `2388091` / `2388093` | Number isn't eligible to receive/verify a registration code (not being migrated) | Register and verify the number instead | | `2388103` | Cannot migrate phone number (multiple causes) | Common fixes: subscribe the destination WABA to webhooks; get the display name approved; complete business verification and review approval; ensure the source/destination WABAs represent the same business; set up a payment account/credit line; or contact support for OBO-model accounts | WABA and OBO Ownership Errors[](https://dualhook.com/docs/api-errors#waba-and-obo-ownership-errors) ---------------------------------------------------------------------------------------------------- | Code | Meaning | Recommended action | | --- | --- | --- | | `2593079` | WABA already marked for migration to a different solution ID | The OBO ownership model is deprecated — contact support | | `2593085` | Invalid WABA for OBO mobility | WABA may already use the WABA-Sharing model, or the OBO request wasn't accepted. OBO is deprecated — contact support | Coexistence Synchronization Errors[](https://dualhook.com/docs/api-errors#coexistence-synchronization-errors) -------------------------------------------------------------------------------------------------------------- These occur in the [Coexistence](https://dualhook.com/docs/coexistence) onboarding flow when syncing contacts/history from the WhatsApp Business app. | Code | Meaning | Recommended action | | --- | --- | --- | | `2593107` | Exceeded the max sync API calls for this number | Contacts and history can each sync only once — offboard and re-onboard to retry | | `2593108` | Sync request made outside the 24h post-onboarding window | Re-onboard the user, then sync within 24 hours | | `2593109` | History sharing is turned off by the business | Live Coexistence still works; the business declined or disabled chat-history sharing | Template Insights Errors[](https://dualhook.com/docs/api-errors#template-insights-errors) ------------------------------------------------------------------------------------------ | Code | Meaning | Recommended action | | --- | --- | --- | | `200005` | Template insights unavailable for this WABA | Insights can't be enabled for this WABA right now | | `200006` | Cannot disable template insights | Insights can't be disabled once enabled | | `200007` | Template insights not enabled | Enable insights first — see Confirming template analytics | Server and Generic Errors[](https://dualhook.com/docs/api-errors#server-and-generic-errors) -------------------------------------------------------------------------------------------- | Code | Meaning | Recommended action | | --- | --- | --- | | `1` | Invalid request or possible server error | Check the WhatsApp Business Platform status page; verify request formatting | | `2` | Temporary downtime or overload | Check the platform status page before retrying | | [`131000`](https://dualhook.com/docs/whatsapp-error-131000) | Message failed due to an unknown error | Retry; if it persists, open a Direct Support ticket | | `131016` | A service is temporarily unavailable | Retry with backoff and a circuit-breaker guard | | `133004` | Server temporarily unavailable | Check the platform status page and `details` before retrying | What to Send Support[](https://dualhook.com/docs/api-errors#what-to-send-support) ---------------------------------------------------------------------------------- When asking Dualhook or Meta support about a send failure, include: * The full `error.code`, `error.message`, `error_data.details`, and `fbtrace_id`. * Whether it came from an API response or a status webhook. * The `wamid` / message ID if Meta accepted the send. * The phone number ID and WABA ID. * The template name + language, if it was a template send. * The timestamp and Graph API version. Do not send message content unless support explicitly needs a redacted payload shape. Dualhook support can usually diagnose setup, permissions, template, or health issues from metadata. Error Handling Strategy[](https://dualhook.com/docs/api-errors#error-handling-strategy) ---------------------------------------------------------------------------------------- **Retryable:** * `4` / `80007` / `130429` / `131056` rate and throughput limits (with backoff) * `1` / `2` / `131000` / `131016` / `133004` transient server failures * Network timeout/connectivity errors **Usually non-retryable without payload, access, or account changes:** * `100` / `131008` / `131009` / `131051` validation errors * `0` / `10` / `190` / `200` / `131005` auth and permission errors * `132000` / `132001` / `132012` template errors * `131047` service-window violations (send a template instead) * `131049` / `131050` / `131063` marketing preference or policy failures * `368` / `130497` / `131031` integrity/policy restrictions * `137000` identity-key mismatches (re-verify the recipient) Suggested Retry Policy[](https://dualhook.com/docs/api-errors#suggested-retry-policy) -------------------------------------------------------------------------------------- 1. Use exponential backoff with jitter. 2. Cap retry attempts for transient classes (3–6 attempts). 3. Send non-retryable errors directly to a dead-letter/ops queue. 4. Include idempotency keys in sender jobs to avoid duplicate sends. 5. Always store `fbtrace_id` for support escalation. Dualhook surfaces webhook-side delivery errors and management alerts in monitoring views. Message-send API failures happen in your backend caller and should be logged there with full Graph error metadata. Related ------- * [Messaging Overview](https://dualhook.com/docs/messaging-overview) Core messaging endpoint, text messages, formatting, delivery statuses, and common errors. * [Messaging Webhook](https://dualhook.com/docs/messaging-webhook) Real-time webhook events for inbound messages, delivery statuses, and errors. * [Capacity, Quality & Limits](https://dualhook.com/docs/capacity-quality-limits) API rate limits, messaging limit tiers, quality rating, and scaling guidance. [Browse more docs](https://dualhook.com/docs) [Start Free Trial](https://dualhook.com/sign-up) ---