Description
What is appsecret_proof?
App secret proof (appsecret_proof) is an extra parameter you add to Graph API requests to show that the caller knows the app secret, not just the access token. Its value is the HMAC-SHA256 of the access token, keyed with the app secret and written as lowercase hex. Meta recomputes the same HMAC from the token it received and your app's secret, and rejects the call if the two do not match.
The reason is that an access token is a portable bearer credential. A token copied from a log file, a misconfigured client or a stolen laptop works from any machine. With proofs enforced, the token alone is no longer enough: an attacker also needs the app secret, which never leaves your servers. For WhatsApp Cloud API integrations that run on a long-lived system user access token, this is a cheap second lock on the most valuable credential you hold.
Sending a proof is optional until you switch on Require App Secret under App settings → Advanced → Security in the App Dashboard. From then on, calls without a valid proof fail, which turns the proof from a nice-to-have into a control. It is the outbound counterpart of X-Hub-Signature-256, the HMAC Meta uses in the other direction to sign the webhooks it sends you; both are built on HMAC and keyed with the same secret.
Key takeaways
- appsecret_proof is the hex HMAC-SHA256 of an access token, keyed with your app secret, sent as a parameter on Graph API calls.
- It ties a token to your app's secret, so a token leaked from logs or a client is not enough on its own.
- Turning on Require App Secret makes the proof mandatory; server calls without a valid proof then fail.
- Recompute the proof whenever the token or the app secret changes: it is a digest of exactly that token.
- It protects calls you make to Meta; X-Hub-Signature-256 is the matching check on webhooks Meta sends you.
Usage
Appsecret_proof in code
import crypto from "node:crypto";const token = process.env.WA_SYSTEM_USER_TOKEN; // both values come from your secret storeconst proof = crypto.createHmac("sha256", process.env.META_APP_SECRET) // key: the app secret.update(token) // message: the exact token.digest("hex");const url = new URL("https://graph.facebook.com/v26.0/{PHONE_NUMBER_ID}/messages");url.searchParams.set("appsecret_proof", proof);const res = await fetch(url, {method: "POST",headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },body: JSON.stringify({messaging_product: "whatsapp",to: "919812345678",type: "template",template: { name: "order_shipped", language: { code: "en" } },}),});
Compute the proof in the same helper that attaches the token, so the two can never drift apart.
Mechanics
How does appsecret_proof work?
step 1 — Take the exact access token
Use the token string you are about to send, byte for byte: no
Bearerprefix, no surrounding quotes, no trailing newline.step 2 — HMAC it with the app secret
Compute HMAC-SHA256 with the app secret as the key and the token as the message, then encode the 32-byte result as 64 lowercase hex characters.
step 3 — Send it with the call
Add
appsecret_proof=<hex>as a query or form parameter on the Graph API request, alongside the token in theAuthorizationheader or theaccess_tokenparameter.step 4 — Meta recomputes and compares
Graph computes the same HMAC from the token it received and your app's secret. A match lets the call through; a mismatch returns an error and nothing is executed.
step 5 — Enforce it
Once every server call sends a proof, turn on Require App Secret. Calls without one then fail, so a token used anywhere else is useless without the secret.
Properties
Appsecret_proof: key facts and limits
- Parameter
- appsecret_proof (query or form field)
- Value
- hex HMAC-SHA256, key = app secret, message = token
- Length
- 64 lowercase hexadecimal characters
- Enforcement
- App settings → Advanced → Security → Require App Secret
- Changes when
- The token or the app secret changes
- Inbound counterpart
- X-Hub-Signature-256 on webhooks
Caveats
Common mistakes with appsecret_proof
Hashing the wrong string
Including
Bearer, a trailing newline (the classicechomistake) or a different token from the one actually sent gives a proof that looks right and never matches. Hash the exact token, withprintf '%s'in shell scripts.Swapping key and message
The app secret is the HMAC key and the token is the message. Reversing them, or using plain SHA-256 with no key, produces a digest Graph will never accept.
Reusing a proof after a token change
A proof belongs to one token. When a token is refreshed or replaced, or the app secret is reset, recompute it, or calls start failing once enforcement is on.
Enabling Require App Secret too early
Any legacy script or client-side call without a proof breaks immediately. Inventory every caller, add proofs everywhere, then switch the setting on.
Notes
Best practices for appsecret_proof
- Treat an "Invalid appsecret_proof" error as a configuration alarm: it usually means the token and secret come from different environments.
- Keep separate Meta apps for staging and production so each environment has its own secret and its own proofs.
- Log whether a proof was attached, never the proof or the token itself.
- Rotate long-lived system user tokens on a schedule even with proofs on: the two controls stack.
Why it matters
Why does appsecret_proof matter?
System user tokens for WhatsApp often never expire and carry the whatsapp_business_messaging and whatsapp_business_management permissions, so a single leak can mean messages sent in your name and templates changed without your knowledge. An appsecret_proof costs one HMAC per request and turns a leaked token from an immediate incident into a much harder attack that also requires your app secret.
Example
Example: A stolen laptop that was not an emergency
A Bengaluru direct-to-consumer brand once let its support tooling print full request URLs, tokens included, into a shared log viewer. After an internal audit the team fixed the logging and enabled proofs: every Graph API call from its order-notification service now carries appsecret_proof, and Require App Secret is switched on. Months later a contractor's laptop holding old log exports is stolen. Any token in those exports would still need the app secret, which lives only in the production vault, so the response is a planned token rotation the next morning rather than a midnight incident.
Questions
App Secret Proof (appsecret_proof) FAQs
- How do I generate appsecret_proof?
- Compute HMAC-SHA256 of the access token using the app secret as the key, and send the hex digest. In Node:
crypto.createHmac("sha256", appSecret).update(token).digest("hex"); in PHP:hash_hmac('sha256', $token, $appSecret). - Is appsecret_proof required for the WhatsApp Cloud API?
- Only if you turn on Require App Secret for your app. Without that setting the proof is optional, though a proof you do send must be valid; with it, server calls lacking a valid proof fail.
- What does "Invalid appsecret_proof provided in the API argument" mean?
- The proof Meta computed does not match the one you sent. Usually the token and the secret come from different apps or environments, the secret was reset, or the token was hashed with extra characters such as a newline.
- What is the difference between appsecret_proof and X-Hub-Signature-256?
- appsecret_proof protects calls you make to Meta; X-Hub-Signature-256 protects webhooks Meta sends to you. Both are HMAC-SHA256 keyed with the app secret, computed over the token and the raw body respectively.