Set up your webhook consumer app, implement CRC validation, and start receiving events
This guide walks you through setting up a webhook consumer app, implementing the Challenge-Response Check (CRC), securing incoming events, and registering your webhook with X.
To register a webhook with your X app, you need to develop, deploy, and host a web app that receives X webhook events and responds to CRC security requests.
The Challenge-Response Check (CRC) is how X validates that the callback URL you provided is valid and that you control it. Your web app must correctly respond to CRC requests to register and maintain your webhook.
Each POST request from X includes an x-twitter-webhooks-signature header that enables you to confirm that X is the source of the incoming webhook.To verify the signature:
Get the x-twitter-webhooks-signature header value from the incoming request
Create an HMAC SHA-256 hash using your consumer secret as the key and the raw request body as the message
Base64 encode the hash and prepend sha256=
Compare your computed value to the header value — they should match
import hmacimport hashlibimport base64def verify_signature(payload, signature_header, consumer_secret): """ Verify that a webhook POST request actually came from X. Args: payload: The raw request body (bytes) signature_header: The x-twitter-webhooks-signature header value consumer_secret: Your app's consumer secret Returns: True if the signature is valid """ expected = "sha256=" + base64.b64encode( hmac.new( consumer_secret.encode("utf-8"), payload, hashlib.sha256 ).digest() ).decode("utf-8") return hmac.compare_digest(expected, signature_header)
Once your app can handle CRC checks, register your webhook URL by making a POST /2/webhooks request. When you make this request, X will immediately send a CRC request to your web app to verify ownership.All webhook management endpoints require OAuth2 App Only Bearer Token authentication.
When a webhook is successfully registered, the response includes a webhook ID. This ID is needed when making requests to products that support webhooks (e.g., linking to Filtered Stream, or creating subscriptions for Account Activity).Common failure reasons:
Reason
Description
CrcValidationFailed
Your callback URL did not respond correctly to the CRC check (e.g., timed out, wrong response)
UrlValidationFailed
The callback URL does not meet requirements (e.g., not https, invalid format)
DuplicateUrlFailed
A webhook is already registered by your application for this URL
WebhookLimitExceeded
Your application has reached the maximum number of allowed webhooks
PUT /2/webhooks/:webhook_id — API ReferenceTriggers a CRC check for the given webhook. If the check succeeds, the webhook is re-enabled with valid: true.
Response:A 200 OK response indicates the CRC check was initiated. The valid field reflects the status after the check attempt. You can verify the current status using GET /2/webhooks.
{ "data": { "valid": true }}
Failure reason
Description
WebhookIdInvalid
The provided webhook_id was not found or is not associated with your app
CrcValidationFailed
The callback URL did not respond correctly to the CRC check
For testing purposes, the xurl tool supports temporary webhooks. Install the latest version of the xurl project from GitHub, configure your authorization, then run:
xurl webhook start
This will generate a temporary public webhook URL, automatically handle all CRC checks, and log any incoming subscription events. It’s a great way to verify your setup before deploying. Example output:
Starting webhook server with ngrok...Enter your ngrok authtoken (leave empty to try NGROK_AUTHTOKEN env var):Attempting to use NGROK_AUTHTOKEN environment variable for ngrok authentication.Configuring ngrok to forward to local port: 8080Ngrok tunnel established! Forwarding URL: https://<your-ngrok-subdomain>.ngrok-free.app -> localhost:8080Use this URL for your X API webhook registration: https://<your-ngrok-subdomain>.ngrok-free.app/webhookStarting local HTTP server to handle requests from ngrok tunnel...
If you have more than one web app sharing the same webhook URL and the same user mapped to each app, the same event will be sent to your webhook multiple times (once per web app).
In some cases, your webhook may receive duplicate events. Your webhook app should be tolerant of this and deduplicate by event ID.