Scaling Meta Graph API automation under load
Rate limits, webhook reliability, and the 24-hour message window — what it takes to run Creator at production scale.
Creator's WhatsApp and Instagram automation talks to Meta's Graph API roughly 14 million times a month. At that volume, every constraint Meta documents — and a few they don't — becomes load-bearing. The 200-calls-per-hour-per-app-per-user limit, the 24-hour customer service window, the Standard Access cap on conversation volume, the webhook retry semantics — these aren't quirks to work around. They're the shape of the platform.
The rate-limit math nobody does upfront
Meta's documented limit for the Graph API on Standard Access is 200 calls per hour per app per user, computed on a sliding window. For a creator with 50,000 followers who replies to DMs, that ceiling sounds generous — until you remember that "calls" includes every fetch of conversation context, every media download, every typing-indicator update, every read receipt. A single inbound DM that triggers an LLM-generated reply can fan out to 6–8 Graph calls before the response goes out.
We do three things to stay inside the envelope:
- Aggressive context caching. A creator's profile, recent media list and conversation history get cached at the edge for 90 seconds. Most replies are within that window.
- Coalesce read receipts. If a creator opens 40 DMs in 30 seconds, we send one batched read-receipt update, not 40.
- Per-user token bucket. Each connected creator account has its own bucket sized to 180/hour (60 below the cap, for safety). When a bucket runs dry, non-critical calls queue with exponential backoff.
The 60-call buffer has saved us four times this year. Meta occasionally tightens the limit without notice for a specific account category, and that headroom absorbs the change while we figure out what happened.
Webhooks are unreliable, plan accordingly
Meta retries a failed webhook delivery up to 36 hours with backoff, but "failed" means "didn't get a 2xx within the timeout". A handler that takes 6 seconds to process is failed from Meta's perspective even if it eventually succeeds. Our handler does the bare minimum inline:
app.post('/webhooks/meta', async (req, res) => {
if (!verifySignature(req)) return res.sendStatus(401);
await queue.enqueue('meta-event', {
payload: req.body,
receivedAt: Date.now(),
idempotencyKey: req.headers['x-hub-delivery-id'] as string,
});
res.sendStatus(200);
});
Two hundred microseconds of signature check, an enqueue, and a 200. The actual processing happens in a worker pool with retry, dead-letter and reconciliation. If we miss a webhook entirely — and we do, occasionally, when Meta's edge has a bad five minutes — the reconciliation job fetches conversation deltas every 15 minutes and replays anything we didn't see live.
The 24-hour window is a state machine, not a flag
WhatsApp's customer-service window is 24 hours from the customer's last message. Inside the window, free-form messages are allowed. Outside it, only pre-approved template messages can be sent. This sounds simple. In production it isn't, because the window is per-conversation and shifts on every inbound message, and your UI needs to tell the creator, in real time, which of their 200 open conversations are about to close.
We model the window as a per-conversation state machine with three states (open, closing, closed) and a deterministic transition driven by the inbound timestamp. The closing state fires 60 minutes before expiry and triggers a UI nudge. The closed state forces the composer into template-only mode. Misclassifying a closed conversation as open is a policy violation that Meta tracks against the WABA's quality rating, so the state machine is unit-tested with the kind of intensity we usually reserve for the ledger.
Meta's quality rating is a soft kill switch on your throughput. One bad week of high-frequency template messages to cold conversations and your daily template cap drops 80%. The 24-hour window is not a guideline, it's a survival constraint.
Standard Access vs the conversation cap
Standard Access caps inbound conversations at 1,000 per day. Most creators never hit it. The ones who do — viral moments, product launches, AMA-style sessions — hit it catastrophically, and the API starts returning 80007 errors mid-conversation. We watch the day's running count and, at 850, fire a warning to the creator: you're approaching the cap, expect ingestion delays. At 980, we switch the worker pool from real-time to deferred mode and let webhooks queue rather than drop. Meta backfills via the conversation history API once the window resets at 00:00 UTC.
The proper fix is moving the WABA to Advanced Access, but that requires a Meta business verification that takes 2–6 weeks. We pre-emptively flag accounts trending toward the cap and start the verification before the creator notices the problem.
The thing nobody tells you about webhook subscriptions
Webhook subscriptions on Meta accounts silently expire when a long-lived page access token hits its 60-day boundary. The webhook keeps appearing subscribed in the dashboard. Events just stop arriving. We discovered this the hard way when a creator's automation went quiet at exactly 60 days and 4 hours after onboarding. The fix is a daily token refresh cron and a synthetic ping (post a test event, verify it lands) every 12 hours. If the synthetic ping fails, the on-call rotation pages.
If you're building Meta automation at non-trivial scale, /creator is the product, and admin@airanexus.in reaches the team that wrote it.