All posts
webhookssecurityhmac

IP Allowlisting vs Signature Verification: What Actually Secures a Webhook Endpoint

IP allowlisting feels like a solid first line of defence for a webhook endpoint. It isn't, and understanding why makes the case for signature verification much clearer.

16 September 20264 min read

An endpoint that only accepts requests from a provider's published IP range feels secure. It's the kind of control that shows up early in a security review checklist, and it's genuinely useless against the threat model that actually matters for webhooks.

What IP allowlisting actually protects against

IP allowlisting answers one question: did this request originate from an IP address the provider has published as theirs. That's a real signal, but it's a weak one for two separate reasons.

IP addresses aren't secret, and they aren't hard to spoof in the threat models that matter here. More importantly, this control only stops requests originating from outside the allowed range. It does nothing to verify that the request body wasn't tampered with in transit, and it does nothing to stop a request forged by someone who has, or can spoof, an IP within the range.

Provider IP ranges change. GitHub explicitly documents that their IP ranges shift over time and advises against relying on them for access control. An allowlist built from a snapshot of published ranges either starts silently rejecting legitimate webhooks when the range changes, or gets left permissively wide to avoid that, which defeats the purpose.

What signature verification actually protects against

Signature verification answers a different, more useful question: was this exact request body created by someone who holds the shared secret, and has it been altered since. HMAC-based verification (the mechanism Stripe, GitHub, and Shopify all use) recomputes a hash of the request over the secret and compares it to what the provider sent.

function verifySignature(rawBody: Buffer, signature: string, secret: string): boolean {
  const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
  return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

This doesn't care what IP the request came from. It cares whether the exact bytes received match what someone holding the secret would have produced. Change a single character in the payload in transit, and the signature no longer matches, regardless of where the request originated. Our signature verification guide covers the mechanics of HMAC verification in more depth.

IP allowlisting and signature verification protect against different things. Swapping one in for the other, rather than treating IP allowlisting as an optional extra layer, leaves you without the control that actually matters: proof the request wasn't forged or tampered with.

Where IP allowlisting still earns a place

None of this makes IP allowlisting worthless, just insufficient on its own. As a supplementary layer on top of signature verification, it can reduce noise: scanners and opportunistic bots hitting your endpoint from unrelated IPs get filtered before they even reach your verification logic, which is a minor operational win even though it's not a security boundary.

app.post('/webhooks/provider', (req, res) => {
  // Optional noise reduction, not the security boundary
  if (!isKnownProviderIp(req.ip)) {
    logger.info(`Webhook request from unrecognized IP: ${req.ip}`);
    // Log it, maybe rate-limit it harder, but don't treat this alone as proof of forgery
  }

  // The actual security boundary
  if (!verifySignature(req.rawBody, req.headers['x-signature'], secret)) {
    return res.status(401).send('Invalid signature');
  }

  // ...
});

If you do maintain an IP list, treat it as something to actively refresh against the provider's published source, not something you set once during initial integration and forget about.

The failure mode that makes this concrete

Imagine an endpoint secured only by IP allowlisting, with no signature check. Anyone who discovers (or is granted, even briefly) an IP within the provider's published range, or who routes traffic through infrastructure that happens to sit in that range, can send a request your endpoint will accept as genuine. There's no check on the body's authenticity at all. That's the gap signature verification closes, and it's the reason every major webhook provider builds it into their platform rather than leaving security entirely to network-level controls.


IP allowlisting is a reasonable secondary filter. It was never proof that a request is genuine, and it isn't meant to be. That's what the signature is for.

Webhook Watchtower handles that part automatically, selecting the correct algorithm and encoding per provider preset and defaulting "reject invalid signatures" to on. More on how that fits into the rest of the platform's security model in the security and data docs.

Frequently asked questions

Is IP allowlisting for webhooks pointless?

Not pointless, but it's a supplementary control rather than a primary one. It can reduce noise from obviously unrelated traffic, but it shouldn't be the thing standing between your endpoint and a forged request. Provider IP ranges change, and an allowlist that isn't kept current either blocks legitimate traffic or silently stops doing anything useful.

Why doesn't GitHub recommend IP allowlisting for webhooks?

GitHub's own documentation notes that their IP ranges change over time and explicitly advises against relying on them for access control. An allowlist built against a snapshot of their published ranges can go stale without any warning, either rejecting legitimate webhooks after a range changes or, worse, giving a false sense of security if it's simply left wide open to compensate.

Can I use both IP allowlisting and signature verification?

Yes, and it's a reasonable defence-in-depth choice if you keep the IP list current. Just don't let it replace signature verification. The signature is what proves the request's authenticity and integrity; the IP check, at best, narrows where forged requests could plausibly originate from.

Share this post

Webhook Watchtower

Stop debugging webhooks in logs.

Capture every event, replay failures, and get alerted when deliveries fail, without changing your existing setup.

Try it free →