Back to blog
DeveloperPublished 2026-07-249 min read

Webhooks vs Polling for Link Click Events

There are exactly two ways to find out a link got clicked without a person checking a dashboard: ask repeatedly until the answer changes, or get told the moment it does. The first is polling. The second is a webhook. Both work; they trade latency and request volume for setup complexity in opposite directions.

Comparison of polling versus webhooks for link click events: polling repeatedly asks an API on a timer and wastes most requests finding nothing new, while a webhook pushes each click event to your endpoint the moment it happens

How Polling Works

Call the clicks endpoint on a timer, check whether anything's new since the last check, repeat. It's the simplest possible integration — no public endpoint required on your side, nothing to verify, nothing that can fail to reach you. The cost is built into the mechanism itself: data is only ever as fresh as the poll interval, and most polls return nothing new at all, which still counts against your rate limit budget for zero information gained.

How Webhooks Work

Register a URL once; the shortener sends a request to that URL the instant a click happens, instead of waiting to be asked. Latency drops to near zero, and there's no wasted request volume — a webhook only fires when there's actually something to report. The cost moves to your side: you need a publicly reachable endpoint, a way to confirm an incoming request genuinely came from the shortener and not an attacker, and handling for the case where a delivery gets sent more than once.

A Real Webhook Payload and Handler

A click event payload typically looks like this:

{
  "event": "link.clicked",
  "linkId": "lnk_8f2a1c",
  "shortUrl": "https://go.yourbrand.com/summer-sale",
  "timestamp": "2026-07-24T10:00:00Z",
  "device": "mobile",
  "country": "BD",
  "referrer": "instagram.com"
}

And a handler that actually verifies it's legitimate before trusting it:

const crypto = require('crypto')

function isValidSignature(rawBody, signature, secret) {
  if (!signature) return false
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
  const sigBuffer = Buffer.from(signature)
  const expectedBuffer = Buffer.from(expected)
  if (sigBuffer.length !== expectedBuffer.length) return false
  return crypto.timingSafeEqual(sigBuffer, expectedBuffer)
}

app.post('/webhooks/clicks', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature']

  if (!isValidSignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature')
  }

  const event = JSON.parse(req.body)
  console.log('Click event:', event.linkId, event.timestamp)

  res.status(200).send('ok')
})

Two details here are easy to get wrong. First, this route needs express.raw(), not express.json() — the signature is computed over the exact bytes the sender transmitted, and parsing the body into an object first (which express.json() does automatically) means you're no longer holding those exact bytes when you try to verify them. Second, comparing signatures with crypto.timingSafeEqual instead of a plain === avoids leaking timing information an attacker could use to guess a valid signature byte by byte — but it throws if the two buffers aren't the same length, so that has to be checked first rather than assumed.

Reliability: What Happens When Your Endpoint Is Down

A webhook delivery can fail for reasons that have nothing to do with the event itself — your server was mid-deploy, a network blip, a timeout. Most providers retry failed deliveries automatically, which solves the sender's half of the problem and creates the receiver's: your handler might see the same event delivered twice. The fix is making the handler idempotent — check whether you've already processed this specific event ID before acting on it again, so a duplicate delivery is a no-op instead of a duplicate action.

When Polling Is Still the Right Call

Webhooks aren't strictly better, just better for a specific shape of problem:

  • No publicly reachable endpoint. A script running locally, behind a firewall, or on a machine with no inbound access simply can't receive a webhook — polling is the only option.
  • Low event volume where freshness doesn't matter. A weekly summary report doesn't need near-real-time delivery; a scheduled poll once a day is simpler to build and has nothing to secure.
  • You want the absolute minimum moving parts. No signature verification, no public route, no retry/idempotency handling — polling trades all of that complexity away in exchange for latency you may not even care about.

Using Both Together

In practice, the two aren't mutually exclusive. A common, genuinely reliable pattern: use a webhook for real-time reaction — the actual thing you want to happen the moment a click occurs — and a periodic, low-frequency poll as reconciliation, catching anything a webhook delivery might have silently missed despite retries. The webhook handles the common case fast; the poll is the safety net for the rare case where it didn't.

Frequently Asked Questions

Do I need to verify webhook signatures if my endpoint is only reachable internally? Yes — "internal" network boundaries get crossed more often than assumed, and signature verification costs almost nothing to implement. Treat every inbound webhook as untrusted until verified, regardless of where you think the endpoint sits.

What should my webhook handler return if processing fails on my end? A non-2xx status, so the sender's retry logic knows to try again — swallowing the error and returning 200 anyway tells the sender everything succeeded when it didn't, and the event is effectively lost.

How long should I wait before assuming a webhook delivery isn't coming? That's what the reconciliation poll is for — rather than guessing a timeout, a periodic poll catches anything that never arrived, on whatever cadence matches how much staleness is acceptable for your use case.

Can I test a webhook handler without waiting for a real click? Yes — since the payload and signature are just data, you can construct a fake event yourself, sign it with your webhook secret the same way the sender would, and send it to your own endpoint locally to test the handler end-to-end before anything real touches it.

Where Cut.bd Fits

Whether an integration ends up using webhooks, polling, or both, the same rate-limiting discipline applies either way. See the complete URL shortener API guide for how event delivery fits alongside authentication, endpoints, and redirect mechanics, or check the API reference for what's available on your plan.

Found this useful? Share it.

Try Cut.bd's link shortener — free, no account required.

Shorten a link