← All posts
Case Study

How a 2-Person Agency Monitors X DMs and Mentions Without Paying X's Per-Read API Fees — UnifyPort

In January 2026, a two-person social media agency in Lisbon managed X (Twitter) accounts for four clients — two e-commerce brands, a SaaS startup, and a local restaurant chain. The work was straightforward: monitor each account for incoming DMs and mentions, respond within a few hours, and escalate anything that needed the client’s direct input. The tooling was simple too: a Node.js service polling X’s API every 30 seconds per account, checking for new activity, and routing messages into a shared Slack channel for triage.

Under X’s old pricing — the fixed-tier plans that had been in place since 2023 — this setup cost the agency a flat $100/month on the Basic tier. Predictable, budgetable, and invisible to clients who paid a retainer that covered it.

Then February happened.

The meter switched on

X replaced its fixed-tier pricing with pay-per-use on February 3, 2026. Every API call — reads and writes alike — now had a per-request price tag. There was no more flat monthly ceiling. The agency’s polling architecture, designed for a world where API calls were unlimited within a tier, was suddenly running a meter.

The math was immediate. Four client accounts, each polled every 30 seconds for new DMs and mentions. That’s 8 reads per minute (2 endpoints per account × 4 accounts), or 11,520 reads per day, or roughly 345,600 reads per month — just for monitoring. At X’s initial per-read rate, that came to a number the agency hadn’t budgeted for.

When X revised rates on April 20, owned-resource reads dropped to $0.001 per call. That helped — the monthly monitoring reads dropped to roughly $346. But the same revision raised write requests (replies) from $0.010 to $0.015 each. The agency typically sent 400–600 replies per month across all four accounts; at $0.015 each, that added $6–$9. Not dramatic on its own, but the direction bothered them: every new client account would add another polling loop, and every increase in reply volume would push the bill from both sides.

The real cost wasn’t the rate — it was the architecture

The agency’s founder ran the numbers for a hypothetical Q3: if they onboarded two more clients (a realistic pipeline), the polling reads alone would roughly double. The per-read rate was low, but the volume was structural — a property of how often the service checked for new messages, not how many messages actually arrived. On a quiet Sunday with zero DMs, the polling loop ran just as fast and cost just as much as during a product-launch spike.

This is the part of X’s pay-per-use model that doesn’t show up in pricing comparison posts: the cost of waiting for messages is nonzero, and it scales with your monitoring architecture, not your actual inbound volume. Polling four accounts every 30 seconds means 345,600 reads/month whether those accounts receive 10 messages or 10,000.

Slowing the poll interval was the obvious fix. The agency tried 2-minute intervals for a week. Response time visibly degraded — a DM could sit unread for nearly 4 minutes worst-case, and two of their clients had explicitly contracted for “under 2 hours” response SLAs. The delay didn’t violate the SLA, but it changed the feel of the service from “nearly real-time” to “eventually.” They reverted after three days.

Switching from pull to push

The fix wasn’t optimizing the poll — it was eliminating it. The agency connected all four client X accounts to UnifyPort, which pushes inbound DMs and mentions to a webhook as they arrive. No polling loop, no per-read meter, no cost that scales with checking frequency.

The architectural change looked like this:

Before (polling):
  Service ──► X API (read DMs)      ×4 accounts  ×2/min  = 345,600 reads/mo
  Service ──► X API (read mentions)  ×4 accounts  ×2/min  = 345,600 reads/mo
  Total: ~691,200 billable API reads/month

After (webhook push):
  X account ──► UnifyPort ──► POST /webhook ──► Slack    (client A)
  X account ──► UnifyPort ──► POST /webhook ──► Slack    (client B)
  X account ──► UnifyPort ──► POST /webhook ──► Slack    (client C)
  X account ──► UnifyPort ──► POST /webhook ──► Slack    (client D)
  Total: 0 X API reads

Each account delivers inbound activity as a message.received event — the same normalized payload the agency would get if they later added WhatsApp or Telegram accounts for those same clients:

{
  "event": "message.received",
  "account_id": "acct_client_a",
  "provider": "twitter",
  "from": "user_7d2e1f",
  "text": "Hey, is the summer sale still on? Saw a post but the link was broken",
  "timestamp": 1750320000,
  "message_id": "x_msg_8a3b2c"
}

The webhook handler verifies each delivery with HMAC-SHA256, then routes by account_id to the correct client’s Slack channel:

app.post("/webhook", (req, res) => {
  if (!verifySignature(req)) return res.sendStatus(401);

  const evt = req.body;
  if (evt.event === "message.received") {
    const channel = clientChannelMap[evt.account_id];
    slack.postMessage(channel, {
      text: `[${evt.provider}] ${evt.from}: ${evt.text}`,
      metadata: { messageId: evt.message_id },
    });
  }
  res.sendStatus(200);
});

Four accounts, one endpoint, one signing secret. The polling service was shut down entirely.

What changed

The agency’s X-related API costs went from ~$350/month (and climbing with each new client) to zero metered reads. Response latency actually improved — events arrive within seconds of the message being sent, rather than waiting for the next poll cycle. And onboarding a new client account no longer means adding another polling loop to the billing equation; it means adding one more entry to clientChannelMap.

The two-person team still replies to X DMs and mentions the same way — from Slack, using a reply endpoint that routes back through UnifyPort. The difference is that “monitoring four X accounts” is no longer an API cost that the agency has to model, explain to clients, or worry about scaling. If the pipeline brings two more accounts in Q3, the webhook receives six instead of four. The bill doesn’t know the difference.

For agencies and teams managing multiple X accounts — especially those whose X API costs scale with polling frequency, not message volume — the architectural question isn’t “what’s the cheapest poll interval.” It’s whether pulling is the right model at all when a push-based webhook can deliver the same messages without running the meter.