DEV Community

A Webhook Tells You Something Happened, Not That You Have Everything

Nabeel Hassan on October 03, 2026

I build VoiceDash, a white-label portal where agencies show their own clients the calls their AI voice agents handled: transcripts, recordings, ana...
Collapse
 
muhammad_turnergane_7ddc profile image
Muhammad Turner Gane •

"A webhook tells you something happened, not that you have everything" is the cleanest way I've seen this put. Billing has exactly the same shape. The webhook is the fast path, the provider's list is the truth, and the only way to know you're in sync is to go and ask on a schedule.

The one thing I'd add from the billing side is to have the reconcile job report what it fixed instead of fixing it quietly. If it keeps finding gaps, that number is telling you the webhook path is broken somewhere, like a signing secret that changed in a deploy or a handler that returns 200 before the write lands. It's much better to notice that from a count going up than from a customer opening an empty dashboard.

Collapse
 
nabeelbaghoor profile image
Nabeel Hassan •

Thank you, and that addition is a really good one. Right now the sync in VoiceDash already counts what it pulls in, but that number only goes back to the screen as "Imported N new calls" for whoever clicked refresh. Your framing makes me want to treat it as a health signal instead: if routine syncs keep finding calls the webhook should have delivered, that is the webhook path telling you something broke, and a rotated secret or a handler that answers 200 too early is exactly the kind of failure that stays silent otherwise. A count going up is a much kinder way to find that out than a customer asking where yesterday's calls went.

Collapse
 
muhammad_turnergane_7ddc profile image
Muhammad Turner Gane •

That's exactly it. One small thing that makes it easy to act on is storing which path wrote each call, webhook or sync. Then the health check is just "how many calls did sync create in the last day", and in a healthy week that should sit at zero or close to it. If it jumps right after a deploy, you know where to look first.

Thread Thread
 
nabeelbaghoor profile image
Nabeel Hassan •

That is a neat way to make it measurable, and right now VoiceDash cannot answer it. A call row has no field saying which path created it, so the count I described only exists for the one sync that just ran and then disappears into a toast. A small source column would fix that.

The one subtlety is that both paths can reach the same call. The unique key on agent plus call id means whichever gets there first creates the row, and the other finds it already there. So the column really records the first writer, which is exactly what this check wants: if sync keeps winning, the webhook is late or not arriving at all, and a jump right after a deploy points straight at what changed.

Collapse
 
beusebiu profile image
Eusebiu Balan •

Billing pushed me to the same place. Lemon Squeezy webhooks are the fast path, and every few minutes a job asks their API again about accounts whose local state looks wrong: a subscription id sitting on a free account, or a subscription that expired in the last day.

Much cheaper than walking the full list, though it only catches the gaps you can see in your own data.

Collapse
 
nabeelbaghoor profile image
Nabeel Hassan •

That is a really clean version of it, and I like that you named the limit yourself. Checking only the rows that look suspicious is cheap precisely because your own data picks the targets, so a gap that leaves no trace locally slips through. I ended up with the other trade on the calls side: every agent keeps a watermark, each sync reads only calls that started after it minus a two hour overlap, so a routine run is usually one request, and there is a separate full re-read for when I stop trusting the watermark. Yours is probably the better fit for billing, where the set of interesting accounts is small and the cost of a missed state is high.

Collapse
 
vasyl_kyryliuk profile image
Vasyl Kyryliuk •

I'm thinking through billing imports for VAT Engine. What triggers a full re-read when a record is corrected outside the two-hour overlap? Is there an updated-at cursor from the provider, or do you need a separate periodic sweep to catch those changes?

Collapse
 
nabeelbaghoor profile image
Nabeel Hassan •

Good question, and the honest answer is that in VoiceDash nothing catches it automatically. The watermark is on the call's start time, so a routine sync only looks at calls that started after it, minus the two hour overlap. The full re-read exists, but it is manual: it is what the Sync Calls button triggers, and it reads the agent's whole history.

Even then it is built for missing calls, not corrected ones. It inserts calls it has never seen and fills in calls it stored mid-call (duration still 0), but it does not rewrite a finished row that already exists. That fits calls, because a finished call barely changes after the fact.

Billing is the opposite case, where corrections are the whole point. If your provider exposes an updated-at filter, I would run a second cursor on that next to the creation cursor, and still keep a slow periodic sweep over a recent window, because an updated-at cursor only catches the edits the provider actually stamps.

Collapse
 
jeemmo profile image
Azeem Javed •

This matches what I landed on for company-filing alerts: a live stream for speed, plus a daily job that re-checks every watched company and catches whatever the stream missed during reconnects or outages. One detail worth stealing: the first time the daily job sees an item, record it as a silent baseline instead of alerting, so a backfill or a newly added account doesn't flood users with old events.

Collapse
 
nabeelbaghoor profile image
Nabeel Hassan •

The silent baseline is a great detail, and it turns out the import path in VoiceDash gets that almost by accident. The webhook handler is the only place that forwards a call on to the agency's own webhook URLs. The importer just writes rows, so pulling in months of history on connect fills the dashboard without firing a single downstream automation for calls that happened weeks ago. I had not thought of it as a deliberate rule until reading your comment, but it is one: backfill is for the record, live events are for reactions.

Collapse
 
elijahbrown profile image
Elijah Brown •

Filling the first sync during onboarding so the dashboard is not empty is the useful product fix. When you invent demo agency and client fixtures for that path, use reserved fiction phones (US 555-0100 to 555-0199, UK 020 7946 0xxx) and domains you own, so a mis-scoped export during a workspace handoff cannot publish a real contact.

Collapse
 
nabeelbaghoor profile image
Nabeel Hassan •

Good tip, and worth saying for anyone building demo data. In this case the onboarding fill is not fixtures at all: it is the customer's own past calls, read from Retell with their API key and stored under their agent, so nothing invented goes into a workspace. But for screenshots, seeds and docs I fully agree, the 555-0100 to 555-0199 range and domains you own are the safe default, because fixture data has a way of ending up somewhere real.