Why the Meta Console Comes First
Every WhatsApp to Salesforce integration starts in the same place, and it is not Salesforce. Before your org can push one message through Meta's Cloud API, Meta needs to know exactly which business is calling, which WhatsApp Business Account (WABA) it owns, which phone number it may send from, and what its access token is allowed to touch. That identity work is spread across three different consoles: the App Dashboard at developers.facebook.com, Business Settings at business.facebook.com/settings, and WhatsApp Manager at business.facebook.com/wa/manage. Tokens and system users live in Business Settings, webhooks live in the App Dashboard, templates and billing live in WhatsApp Manager. Mixing those three up is the single biggest time sink we see on client projects.
One note on the word OAuth. Meta's browser flow for WhatsApp is embedded signup, built on Facebook Login for Business; it links a Meta Business Account and a WABA to the tool requesting access, and Salesforce's native WhatsApp channel wraps exactly that popup. For a custom Cloud API build you skip the browser flow entirely and authenticate with a permanent system user token, which means the Salesforce side needs an External Credential and a Named Credential but no Auth Provider, because there is no redirect or refresh dance to run.
This article is the zoomed-in first step of our full technical guide at cloudsheer.com/integrations/whatsapp/technical-guide. Cloudsheer has shipped this exact setup across 6 client projects, and the sequence below is the one that survived all six.
What You Need Before You Start
- A Facebook login with admin access to a Meta Business Portfolio. The app wizard can create one on the fly, but corporate WABAs belong in a portfolio your company controls, not one hanging off a personal profile.
- Your legal business name, address, phone, and website exactly as they appear on registration documents. Business verification compares them character by character.
- A phone number that is not active on the consumer WhatsApp app or the WhatsApp Business app. If it is, delete that account first or Cloud API registration will fail.
- A publicly reachable HTTPS webhook endpoint with a CA-signed TLS certificate. Self-signed certificates are explicitly not supported.
- A Salesforce org where you can create External Credentials, Named Credentials, and permission sets. Digital Engagement licenses only matter if you choose the native channel.
- A password manager open and ready. Two values in this tutorial are displayed exactly once.
Step 1: Create the Meta App and Link a Business Portfolio
Go to developers.facebook.com > My Apps > Create App. In the current flow you do not pick an app type first; you pick the use case Connect with customers through WhatsApp, which creates a Business-type app behind the scenes (the old flow was app type Business, then adding the WhatsApp product tile). When prompted, select or create a business portfolio - connecting one is required to use WhatsApp. You land on Customize use case > Connect on WhatsApp > Quickstart.
- While you are here, collect the App ID and App Secret from App settings > Basic. The Secret hides behind a Show button and requires re-entering your Facebook password. Copy both into a password manager immediately; the App Secret signs every inbound webhook via X-Hub-Signature-256, so treat it like a production password from this second onward: no email, no Slack, no spreadsheet.
Step 2: API Setup, the Test Number, and Your First Message
Click Start using the API, or open WhatsApp > API Setup in the left nav. Meta provisions a free test business phone number for the app, and everything on this page matters.
- Copy the Phone number ID and the WhatsApp Business Account ID shown here. Every API call uses the Phone number ID, never the raw phone number; the send endpoint is POST https://graph.facebook.com/v23.0/{PHONE_NUMBER_ID}/messages.
- Click Generate access token. This token is temporary and expires in 24 hours. It exists for today's testing only; anything you deploy with it will 401 tomorrow.
- In the To field choose Manage phone number list and add up to 5 recipient numbers. Each recipient must confirm an OTP code sent to their WhatsApp before they can receive anything; every other number gets error 131030.
- Click Send message to fire the pre-approved hello_world template, which the API expresses as {"type":"template","template":{"name":"hello_world","language":{"code":"en_US"}}}. When it lands on your phone, the outbound half of the integration is proven.
Step 3: Configure the Webhook
Inbound messages arrive as webhooks, and your endpoint must already be live before you touch the dashboard, because Meta validates it synchronously the moment you save.
- Go to App Dashboard > WhatsApp > Configuration. Under Webhook click Edit, then enter your Callback URL and a Verify token - an arbitrary string you invent here and also store in your endpoint code.
- Click Verify and save. Meta immediately sends GET ?hub.mode=subscribe&hub.verify_token=<your token>&hub.challenge=<value>. Your endpoint must check the token and return the raw hub.challenge as plain text with a 200, not JSON, or the save fails with "The callback URL or verify token couldn't be validated".
- Click Manage and subscribe to the messages webhook field. Delivery and read statuses arrive on the same field, so one subscription covers both.
- Every event POST carries the header X-Hub-Signature-256: sha256=<HMAC-SHA256 of the raw body using your App Secret>. Validate it and respond 200 fast; on anything else Meta retries immediately, then with decreasing frequency for up to 36 hours, and then drops the event.
Step 4: Create a System User and Generate the Permanent Token
The 24-hour token dies quietly, and it takes demos down with it. Production authenticates as a system user, a non-human identity that lives in Business Manager, not in the App Dashboard.
- Go to business.facebook.com > Business Settings (the gear icon) > Users > System users > Add. Name it something obvious like sf-whatsapp-integration, give it the Admin role (or Employee for tighter scoping), and confirm.
- Select the new system user > Assign assets > Apps > your app > enable Manage app (Full control). Employee system users also need the WhatsApp Account asset: WhatsApp Accounts > your WABA > Full control. Admin system users inherit all WABAs automatically.
- Click Generate token > select your app > choose an expiration of 60 days or Never > tick exactly three permissions: business_management, whatsapp_business_management, and whatsapp_business_messaging > Generate token.
- Copy the token now. It is displayed once and Business Manager never redisplays it; close the dialog without copying and your only option is generating a new one. Paste it straight into your secret store.
- For later rotation: generate a new token, update the Salesforce External Credential parameter, then revoke the old one from the system user's token list. Inspect any token's scopes and expiry at developers.facebook.com/tools/debug/accesstoken.
Step 5: Add a Real Number, Verify the Business, Get Templates Approved
The test number is a sandbox with a 5-recipient wall. Going live requires three separate Meta approvals that can run in parallel, so start all of them early.
- WhatsApp > API Setup > Add phone number: enter the number, set the display name, and verify ownership via an SMS or voice call code. Display-name approval and Cloud API registration are separate steps; an approved name on an unregistered number still throws error 133010.
- Register the number with POST https://graph.facebook.com/v23.0/{PHONE_NUMBER_ID}/register and body {"messaging_product":"whatsapp","pin":"<your 6-digit two-step verification PIN>"}. This endpoint is rate limited to 10 requests per number per 72 hours and returns 133016 beyond that, so never script blind retries.
- Business verification: Business Settings > Business info > verification status > View details opens Security Center > Start verification. Provide details exactly as registered and upload documents if auto-verification fails. Expect 1 to 5 business days, occasionally closer to 10. Unverified portfolios are capped at 250 business-initiated conversations per rolling 24 hours; verification lifts you to 1,000 and unlocks higher tiers.
- Templates: WhatsApp Manager > select your WABA > Account tools > Message templates > Create template. Pick MARKETING, UTILITY, or AUTHENTICATION (Meta validates and may quietly recategorize), a lowercase underscore name, a language, and a body with {{1}} placeholders plus sample values. Review takes up to 24 hours but is often minutes. Add a payment method in WhatsApp Manager billing once you leave the test number, or sends fail with 131042.
Step 6: Wire the Token into Salesforce
In Salesforce this is not an Auth Provider job; there is no refresh flow to configure. The permanent token becomes a Bearer header attached to a Named Credential.
- Setup > Named Credentials > External Credentials tab > New. Label it WhatsApp_Meta, Authentication Protocol = Custom.
- Create a Principal (Named Principal) and add an Authentication Parameter named token with the system user token as its value. Salesforce encrypts it at rest and keeps it out of debug logs.
- Add a Custom Header on the External Credential: Name = Authorization, Value = {!'Bearer ' & $Credential.WhatsApp_Meta.token}.
- Create the Named Credential itself: URL https://graph.facebook.com, External Credential WhatsApp_Meta, and leave Generate Authorization Header unchecked, since your custom header does that job.
- Create a permission set, add External Credential Principal Access for the principal, and assign it to your integration user. Skip this and callouts fail even though every screen looks configured.
- No Remote Site Setting is needed with Named Credentials. For inbound, expose an Apex REST class on a public site guest user: the @HttpGet method echoes hub.challenge when hub.verify_token matches, the @HttpPost method validates X-Hub-Signature-256 against the raw body and returns 200 fast. Put middleware in front of Salesforce for production volume.
- The no-code alternative: Setup > Messaging Settings > New Channel > WhatsApp (Enhanced), which runs on Messaging for In-App and Web and needs Service Cloud plus the Digital Engagement add-on. Salesforce manages Meta's tokens and webhooks for you, but a WABA created outside that wizard cannot be attached to the channel, so decide which numbers go native before creating anything in Meta.
Step 7: Smoke Test the Full Loop
Prove both directions before writing any real code. From Developer Console, run an anonymous Apex callout and watch each layer respond.
- POST to callout:WhatsApp_Meta_NC/v23.0/{PHONE_NUMBER_ID}/messages with header Content-Type: application/json and the hello_world template body, aimed at one of your OTP-verified recipients, or any WhatsApp number once your real number is registered.
- A 200 response containing a messages array whose id starts with wamid. proves the token, the header formula, principal access, and the endpoint are all correct in one shot.
- Reply to that message from the phone. Within seconds your webhook should receive a POST with object whatsapp_business_account and a messages entry inside entry[].changes[]. That inbound reply also opens the 24-hour customer service window, during which free-form messages are allowed without a template.
- Finally, paste the token into the Access Token Debugger and confirm all three permissions are present and the expiry reads Never, or your chosen date with a rotation reminder already on the calendar.
Common Errors and How to Fix Them
- 190 (OAuthException, HTTP 401) "Error validating access token: Session has expired": your 24-hour temporary token died. Fix: generate a system user token with Never expiry and both WhatsApp permissions, then update the External Credential parameter.
- (#10) "Application does not have permission for this action": the token lacks whatsapp_business_messaging or whatsapp_business_management, or the system user was never granted the WABA asset. Fix: regenerate the token with both permissions and Full control asset assignment.
- 100 "Unsupported post request. Object with ID ... does not exist": you sent the raw phone number or the WABA ID where the Phone number ID belongs, or the token cannot see that WABA. Fix: copy the Phone number ID from WhatsApp > API Setup.
- 131030 "Recipient phone number not in allowed list": you are still on the test number, which only reaches its 5 OTP-verified recipients. Fix: add the recipient in Manage phone number list, or register a real business number.
- 131047 re-engagement rejection: more than 24 hours have passed since the customer's last message, so free-form sends are refused. Fix: send an APPROVED template; every inbound customer message resets the window.
- 132001 "Template name does not exist in the translation": the name or language code does not exactly match an approved template, for example sending en_US when it was approved as en. Fix: match both strings exactly.
- 133010 "Phone number not registered": the number was verified but never registered on Cloud API. Fix: POST /register with your 6-digit PIN, and remember the 10-per-72-hours limit that returns 133016.
- "The callback URL or verify token couldn't be validated" on webhook save: the endpoint is not publicly reachable, the certificate is self-signed, the verify token mismatches, or hub.challenge came back as JSON. Fix all four, then click Verify and save again.
Security Checklist Before You Ship
- The permanent token lives only in the External Credential principal parameter, never in Apex source, custom settings, custom metadata, or a post-sandbox-refresh note.
- App Secret signature validation runs on every inbound POST, and any request with a bad X-Hub-Signature-256 is rejected before processing.
- The Verify token is a long random string, not test123, and is stored with the same care as a password.
- Principal access is granted through one dedicated permission set assigned only to the integration user, not spread across admin profiles.
- A rotation runbook exists: generate the new token, update the principal parameter, revoke the old token, confirm scopes in the Access Token Debugger.
- Webhook processing is idempotent, because Meta retries non-200 responses for up to 36 hours and duplicates will arrive.
- The system user carries the minimum role that works, and its asset list contains only this app and this WABA.
Where This Fits in the Full WhatsApp (Meta) Build
Credentials are step one of roughly six. After this you still design the conversation data model in Salesforce, build the send service and template sync, implement inbound routing with the 24-hour window logic, and load test the webhook path. The full sequence, including the native-versus-custom decision and how embedded signup fits when Salesforce's channel wizard drives it, lives in our technical guide at cloudsheer.com/integrations/whatsapp/technical-guide.
If you build with Claude Code, we packaged this playbook as a free skill: run npx skills add shivamgoel-cloudsheer/Claude-skills --skill whatsapp-salesforce and your agent gets the same endpoints, error tables, and Salesforce wiring patterns from this article while it writes your integration code.
How Cloudsheer Can Help
Cloudsheer has delivered WhatsApp and Salesforce integrations across 6 client projects, on both the custom Cloud API pattern described here, a Meta developer app plus a system user permanent token in an External Credential Bearer header behind a Named Credential to graph.facebook.com, and the native enhanced channel on Messaging for In-App and Web. We know where Meta's review queues stall, which errors are configuration and which are policy, and how to plan number and WABA ownership so you are not locked out of the native channel later.
If you want this live in weeks rather than quarters, with a credential architecture your admins can actually maintain, we will scope it with you. Book a free discovery call at cal.com/cloudsheer-consulting/30min.
