X API Account Activity

Webhook CRC Validation, Demystified

Before X delivers account activity events to your webhook, it must confirm that you own the endpoint. It does this with a Challenge-Response Check: a GET request carrying a crc_token that your server must sign with your consumer secret using HMAC-SHA256 and return within 3 seconds.

How the handshake works

X PlatformYour ServerGET /webhook?crc_token=abc123HMAC-SHA256(secret, crc_token)200 OK {"response_token":"sha256=..."}Webhook marked valid

X repeats this check roughly every 24 hours. If your endpoint fails, the webhook is marked invalid and event delivery stops until it passes again.

Implementation checklist

    Try it: response_token playground

    Everything runs locally in your browser using the Web Crypto API. Nothing is sent anywhere. Never paste a production secret into any website; use the demo values here.

    Paste a crc_token and press Compute.
    Reference server code (Node.js)
    const crypto = require('crypto');
    app.get('/webhook', (req, res) => {
      const hmac = crypto
        .createHmac('sha256', process.env.CONSUMER_SECRET)
        .update(req.query.crc_token)
        .digest('base64');
      res.json({ response_token: 'sha256=' + hmac });
    });

    Why CRC exists

    CRC validation proves two things at once: that your endpoint is alive and responsive, and that whoever operates it holds the app's consumer secret. Because only your app and X know the secret, a correct HMAC signature is cryptographic proof of ownership. This prevents attackers from registering webhook URLs they do not control and stops X from delivering sensitive account activity to dead or hijacked endpoints.

    Common failure causes: responding slower than 3 seconds, hashing the wrong value, using an API key instead of the consumer secret, hex-encoding instead of base64, or omitting the sha256= prefix in the JSON body.

    Enjoy this tool? Build your own with Super