Twitter/X Webhook API for Real-Time Notifications

Twitter/X Webhook API for Real-Time Notifications

Real-time Twitter monitoring through webhooks represents the gold standard for building scalable, responsive applications that need instant notification of Twitter activity. Unlike polling-based approaches that check for new content at regular intervals, webhooks provide event-driven notifications as soon as new activity is detected.

This comprehensive technical guide walks through implementing webhook-based Twitter monitoring, from understanding the fundamental architecture to building production-ready systems with proper error handling, rate limiting, and security. Whether you're building trading bots, social media dashboards, or notification systems, mastering webhook implementation is essential for professional-grade real-time monitoring.

Understanding Webhook Architecture for Twitter Monitoring

Webhooks fundamentally change how applications consume Twitter data by inverting the traditional request-response pattern. Instead of your application repeatedly asking "are there new tweets?", Twitter (or a monitoring service) proactively sends a notification when relevant activity occurs.

Traditional polling approaches suffer from inherent limitations that webhooks solve:

Basic Webhook Implementation

Here's a production-ready webhook receiver using Node.js and Express:

const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');

const app = express();

// Raw body parser for signature verification
app.use('/webhook', bodyParser.raw({ type: 'application/json' }));

// Webhook signature verification
function verifyWebhookSignature(payload, signature, secret) {
    const expectedSignature = crypto
        .createHmac('sha256', secret)
        .update(payload)
        .digest('hex');
    
    if (!signature || signature.length !== expectedSignature.length) return false;
    return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expectedSignature)
    );
}

// Main webhook handler
app.post('/webhook/twitter', async (req, res) => {
    try {
        // Verify webhook signature
        const signature = req.headers['x-signature'];
        if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
            return res.status(401).json({ error: 'Invalid signature' });
        }
        
        // Parse JSON payload
        const payload = JSON.parse(req.body.toString());
        
        // Process tweet event
        await processTweetEvent(payload);
        
        // Acknowledge receipt
        res.status(200).json({ status: 'received' });
        
    } catch (error) {
        console.error('Webhook processing error:', error);
        res.status(500).json({ error: 'Processing failed' });
    }
});

async function processTweetEvent(payload) {
    const tweet = payload.data;
    
    // Log received tweet
    console.log(`New tweet from @${tweet.author}: ${tweet.text}`);
    
    // Your own prioritisation, e.g. by account
    if (HIGH_PRIORITY_ACCOUNTS.has(tweet.author)) {
        await handleHighPriorityTweet(tweet);
    } else {
        await queueTweetForProcessing(tweet);
    }
    
    // Store for analysis
    await storeTweetData(payload);
}

app.listen(3000, () => {
    console.log('Webhook receiver listening on port 3000');
});

Webhook Payload Structure

Xanguard delivers a compact JSON payload for each new tweet (example with a fictional account):

{
  "event": "tweet",
  "timestamp": 1758385512234,
  "data": {
    "tweet_id": "1767234567890123456",
    "author": "example_dev",
    "text": "Example tweet text",
    "url": "https://x.com/example_dev/status/1767234567890123456",
    "image_url": null,
    "image_urls": [],
    "is_reply": false,
    "is_quote": false,
    "created_at": 1758385508000,
    "original_tweet_id": null,
    "possibly_sensitive": false,
    "mentions": []
  }
}

Every request carries two headers: X-Signature (hex HMAC-SHA256 of the raw body, keyed with your webhook secret) and X-Webhook-Id (the id of the webhook that matched). When you register a webhook with POST /v1/webhooks you can pass filter_handles and filter_keywords; without them, it fires for every account you track.

Failed deliveries (network errors or 5xx responses) are retried up to 3 times with exponential backoff (1 s, 2 s, 4 s). A 4xx response is treated as a permanent error, so return 200 as soon as you have accepted the payload.

Security Best Practices

A webhook endpoint is a public URL, so treat every request as untrusted until it passes these checks:

  1. Verify the HMAC on the raw body. Compute HMAC-SHA256 of the exact bytes you received, before any JSON parsing, and compare it to X-Signature with a constant-time function (crypto.timingSafeEqual in Node, hmac.compare_digest in Python). Re-serialized JSON will not match.
  2. Reject stale payloads. The body includes timestamp (milliseconds). Drop anything older than a few minutes, which blocks replays of a captured request.
  3. Use a long, unguessable path such as /webhook/3f9c1e..., and rely on the signature, not the sender's IP address, for authentication.
  4. Rate-limit the route (for example with express-rate-limit) so a flood of forged requests cannot exhaust your server.
  5. Keep the secret out of code. It is shown only once, in the response to POST /v1/webhooks. Store it in an environment variable or a secrets manager.

Error Handling, Retries and Idempotency

How Xanguard delivers:

  1. Network errors and 5xx responses are retried up to 3 times, with waits of 1 s, 2 s and 4 s.
  2. A 4xx response is treated as permanent and is not retried.
  3. After 10 consecutive failed deliveries the webhook is switched off automatically. Re-create it once your endpoint is healthy.

What that means for your receiver:

  1. Acknowledge fast, process later. Verify the signature, push the payload onto a queue, return 200, then do the slow work (database writes, trading calls, notifications) in a worker.
  2. Deduplicate on data.tweet_id. A retry after a timeout can deliver the same tweet twice. Keep a short-lived set of seen ids (Redis with a TTL works well) and skip repeats.
  3. Return 200 even when your own processing fails after you have safely stored the payload, and handle the failure on your side with your own retry or dead-letter queue.
  4. Protect downstream services with a circuit breaker, so an exchange or chat API outage does not stall your webhook handler.

Testing Your Webhook

  1. Run your receiver locally and expose it with a tunnel such as ngrok or cloudflared, then register the tunnel URL with POST /v1/webhooks.
  2. Before going live, send yourself a signed test request. Sign a sample body with your secret (openssl dgst -sha256 -hmac "$SECRET") and POST it with curl, including the X-Signature header. Then send it again with a wrong signature and confirm you get a 401.
  3. Unit-test the verifier with a known body, secret and expected hex digest, and test the dedupe path by posting the same payload twice.
  4. Swap the tunnel URL for your production URL once real tweets arrive as expected.

X's Account Activity API vs a Monitoring Webhook

X's own webhook product, the Account Activity API, only delivers events for accounts that have authorized your app, typically your own accounts or your users' accounts. It is built for managing those accounts (DMs, mentions, follows of the account itself), and it is sold as part of X's paid API access. It cannot send you the tweets of arbitrary public accounts you do not control.

A monitoring webhook such as Xanguard's works the other way: you choose any public accounts, and each new tweet from them arrives at your endpoint as a signed POST. If you need to watch accounts you do not own, a monitoring webhook is the tool; if you need to act on behalf of your own accounts, use X's API. For background on the category, see the X monitoring guide.

Conclusion

Implementing robust webhook-based Twitter monitoring requires careful attention to security, performance, reliability, and observability. The patterns and techniques outlined provide the foundation for building systems that can handle high-volume, real-time Twitter intelligence at scale.

For teams looking to implement webhook-based Twitter monitoring without building everything from scratch, Xanguard provides signed webhooks, retries and a REST API out of the box on paid plans, from $19/month for 10 accounts.

Frequently Asked Questions

How do Twitter/X webhook APIs work for real-time notifications?

Instead of your server polling for new tweets, a webhook API sends an HTTP POST request to your server the moment relevant activity occurs. Your server only does work when there is something new, with zero wasted requests.

What are the advantages of webhooks over polling for Twitter/X monitoring?

Webhooks eliminate latency inconsistency, reduce resource waste from empty poll responses, avoid rate limit conflicts, and scale without multiplying requests. Xanguard's webhooks deliver HMAC-signed payloads as soon as a tweet is detected (median detection ~200 ms on paid plans, published at xanguard.tech/speed/).

How do I set up a Twitter/X webhook with Xanguard?

Register your server's endpoint URL with POST /v1/webhooks using the API key from /apikey in @Xanguard_bot, implement HMAC-SHA256 signature verification, and start receiving real-time tweet notifications as HTTP POST requests. Code examples are available in the Xanguard docs.

Are webhook payloads from Xanguard secured?

Yes. All webhook deliveries include an HMAC-SHA256 signature of the raw body in the X-Signature header. Your server verifies this signature using the secret returned when you registered the webhook to confirm the payload is authentic and hasn't been tampered with.

How many webhooks does each Xanguard plan include?

Webhooks are a paid Tweet Alerts feature. Starter includes 1, Growth 2, Pro 5, Business and Scale 10, Enterprise 25 and Ultra 50. The free plan has none.

← Back to Blog

Start Monitoring Twitter in Real-Time

Get Telegram notifications for new tweets from any X account, in a median of ~200 ms on paid plans. Free for 1 account. Paid plans from $19 a month for 10 accounts.