Ledger entryNo. 119418
Webhook Verification Request
Also called: hub.challenge · Verify token handshake · Webhook verification · hub.verify_token
- Webhooks & events
- Foundation
- Developers
- Definition
- The GET Meta sends to a callback URL with hub.mode, hub.verify_token and hub.challenge; the endpoint must echo the challenge with a 200 to be accepted.
Webhooks & events
Mode · sequence
Work it out
Step through the hub.challenge handshake
- YouApp Dashboard
- MetaWebhooks
- Your endpoint/webhooks/whatsapp
Step 7 of 7Response
Your endpointMeta
200 OK
Acknowledge each event quickly.
- Request — solid arrow — a call that expects an answer
- Response — dashed arrow — the answer coming back
- Event — dotted arrow — something happened and is pushed out
Step 7 of 7: Your endpoint to Meta — 200 OK. Acknowledge each event quickly.
What is the webhook verification request?
The webhook verification request is the handshake Meta performs before it will send events to a URL. Whenever you set or edit the callback URL or the verify token of your webhook subscription in the App Dashboard, Meta sends a GET request to that URL with three query parameters: hub.mode set to subscribe, hub.verify_token containing the string you entered, and hub.challenge containing a random value Meta generates.
Your webhook endpoint compares hub.verify_token with the value stored on your server. If it matches and the mode is subscribe, the endpoint responds HTTP 200 with the raw hub.challenge value as the entire body — no JSON wrapper, no quotes, no extra text. Anything else, typically a 403, makes Meta reject the URL, and the configuration is not saved.
The verify token is not a password or an API key: it is an arbitrary string you choose so the endpoint can recognise a handshake you started. Verification runs once per configuration change and proves the URL is reachable and under your control. It is different from signature validation with X-Hub-Signature-256, which authenticates every later POST with your app secret — a verified URL must still check the signature on each event. Nor is it webhook testing: passing the handshake says nothing about how the endpoint handles the events that follow.
Key takeaways
- The webhook verification request is a GET Meta sends when you save a callback URL or verify token, with
hub.mode,hub.verify_tokenandhub.challenge. - Answer HTTP 200 with the bare
hub.challengevalue only whenhub.verify_tokenmatches the token stored on your server. - The verify token is any string you choose; it is not the app secret and does not protect later events.
- Every later event is a POST authenticated by its
X-Hub-Signature-256signature, not by the handshake. - Self-signed certificates are not supported: the callback URL needs a valid certificate from a public CA.
The figures
The webhook verification request: key facts and limits
- Method
- GET (events later arrive as POST)
- Query parameters
- hub.mode, hub.verify_token, hub.challenge
- hub.mode
- subscribe
- Success
- HTTP 200 with the bare hub.challenge as the body
- Failure
- 403 or any other non-200 status
- When sent
- Each time the callback URL or verify token is set or edited
- Certificate
- Valid CA-signed TLS; self-signed not supported
How does the webhook verification request work?
You save a URL and a token
In WhatsApp > Configuration, or through the Application Subscriptions API, you enter the callback URL and a verify token of your choosing.
Meta sends a GET
The request carries
hub.mode=subscribe,hub.challengeandhub.verify_tokenin the query string — and no body.Your endpoint checks
Confirm
hub.modeissubscribeandhub.verify_tokenequals your stored token. Read the parameter names with their dots intact.Echo the challenge
Respond 200 with the
hub.challengevalue as plain text. On a mismatch, respond 403 or another non-200 status.Meta saves the configuration
Only after a correct echo is the URL saved and field subscription available. From then on, events arrive as POSTs signed in
X-Hub-Signature-256.
In code
The webhook verification request in code
import hmacimport osfrom fastapi import FastAPI, Request, Responseapp = FastAPI()VERIFY_TOKEN = os.environ["VERIFY_TOKEN"]@app.get("/webhooks/whatsapp")async def verify(request: Request) -> Response:q = request.query_params # keys keep their dots: "hub.mode"token = q.get("hub.verify_token", "")if q.get("hub.mode") == "subscribe" and hmac.compare_digest(token, VERIFY_TOKEN):return Response(content=q.get("hub.challenge", ""), media_type="text/plain") # 200return Response(status_code=403)
Example: The dots that PHP ate
A Ranchi developer deploys a PHP endpoint and the dashboard keeps rejecting it. The access log shows Meta's GET arriving, but the code reads $_GET['hub.challenge'], which is always empty: PHP converts dots in incoming parameter names to underscores, so the values sit in $_GET['hub_challenge'] and $_GET['hub_verify_token']. After switching keys — and removing a middleware that wrapped every response in JSON — the endpoint returns the bare challenge, the URL saves, and the first test payload arrives a minute later.
Costly mistakes
Common mistakes with the webhook verification request
Returning JSON instead of the bare value
A body like
{"challenge": "1158201444"}, or the value in quotes, fails. Send the challenge alone astext/plain.Losing the dots in parameter names
PHP converts
hub.modetohub_mode, and some routers rewrite query keys too. Log the raw query string once to see exactly what your framework hands you.Auth or a firewall in front of the URL
Basic auth, an API-gateway key or an IP block on the webhook path makes Meta's GET fail. Allow unauthenticated GETs there and protect POSTs with the signature check.
Treating the verify token as security
It only matches a handshake you started. Anyone can send your endpoint a GET; the real protection for events is the
X-Hub-Signature-256check on every POST.Certificate problems
Self-signed certificates are not supported. Use a TLS certificate from a public certificate authority, serve the full chain, and test the URL from outside your own network.
Webhook Verification Request FAQs
- Why does the dashboard say my callback URL could not be validated?
- Usually the token, the response format, the certificate or a blocking proxy is at fault. Check that the token matches, the body is the bare challenge with a 200, the certificate is publicly trusted and nothing in front of the endpoint blocks the GET.
- What should the verify token be?
- Any string you choose; a long random value is best. Store it as a server secret, enter the same value in the App Dashboard, and use a different token for each environment.
- Is the verify token the same as the app secret?
- No, the verify token is any string you choose for the GET handshake. The app secret is issued by Meta and keys the HMAC in
X-Hub-Signature-256on every POST. - How often does Meta send the verification request?
- Only when you set or edit the callback URL or the verify token, not before every event. Keep the GET handler deployed anyway, because every future configuration change runs it again.
Why does the webhook verification request matter?
It is the first thing every WhatsApp integration has to get right, and the most common place new integrations stall: the dashboard shows a terse error and nobody can tell whether the token, the certificate, the routing or the response format is at fault. Knowing exactly what Meta sends and expects turns a lost afternoon into a two-minute fix — and makes clear why passing verification does not mean your events are secure.
Best practices for the webhook verification request
- Generate the verify token randomly and keep it in your secrets store, separate from the app secret.
- Log verification attempts, without the token value, so a failed save is easy to diagnose.
- Run the handshake yourself with curl before saving the URL in the dashboard.
- Keep the GET handler deployed: every future edit of the URL or token runs it again.
Put Webhook Verification Request to work
InfiQ is an official Meta Business Partner. Start a 7-day free trial — no card needed — and see this running on your own number.