Skip to main content
Insyrge - Zoho CRM & Business Automation Consulting
← Back to All ArticlesAI & Business Automation

Webhook Security: Verify Every Incoming Event

How to verify webhook signatures, block replayed requests, protect secrets and handle duplicates without trusting payloads.

•Manav Sharma
Webhook Security: Verify Every Incoming Event

Improve webhook security with HMAC signature checks on the raw body, timestamp limits against replays, secret rotation and duplicate checks by event ID.

A webhook endpoint is a public URL that accepts data and acts on it. Anyone who finds the URL can send a request that looks like a real event: a fake payment confirmation, a forged order or a flood of junk. Webhook security is about making sure every incoming event really came from the expected sender, has not been tampered with, is not an old request replayed, and is processed only once. This guide explains how signature verification works, how to block replayed requests, how to manage secrets and how to handle duplicates without trusting payloads blindly.

Why webhook security matters

Webhooks often trigger actions with real consequences: marking invoices paid, creating orders, updating customer records or starting fulfilment. Without checks, an attacker who learns the endpoint URL could:

  • Send forged events that trigger actions in your systems
  • Modify a genuine event in transit
  • Capture a valid request and resend it later
  • Overload the endpoint with requests

Keeping the URL secret is not enough. URLs leak through logs, browser histories, screenshots and support tickets. Strong webhook security assumes the URL is known and verifies every request.

How signature verification works

Most providers sign each webhook with a shared secret using an HMAC, a hash calculated from the message body and the secret. Your endpoint calculates the same HMAC and compares it with the value sent in a header. If they match, the request came from someone who knows the secret and the body was not changed.

GitHub's guide to validating webhook deliveries is a clear example: GitHub sends the signature in the X-Hub-Signature-256 header, uses an HMAC hex digest, and the signature always starts with "sha256=". Other providers use different header names and formats, so follow each provider's documentation exactly.

Three details catch many teams out:

  • Use the raw body. The signature is calculated over the exact bytes sent. If your framework parses and re-serialises JSON before you verify it, the bytes change and verification fails. Stripe's webhook documentation warns that any manipulation of the raw body causes verification to fail.
  • Compare safely. Python's hmac documentation recommends compare_digest() instead of the == operator to reduce the vulnerability to timing attacks.
  • Reject by default. If the signature header is missing or malformed, return an error and do nothing else.

Worked example: verifying a webhook signature

The simplified, untested Python function below verifies a GitHub-style signature. It is a sketch to show the steps, not production code; adapt header names and formats to each provider.

import hashlib
import hmac

def is_valid_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
    # Expect a header such as "sha256=<hex digest>"
    if not signature_header or not signature_header.startswith("sha256="):
        return False
    expected = "sha256=" + hmac.new(
        secret.encode("utf-8"), raw_body, hashlib.sha256
    ).hexdigest()
    # Constant-time comparison to reduce timing attack risk
    return hmac.compare_digest(expected, signature_header)

In the request handler, read the raw body before any parsing, call this function, and return an error if it fails. Only after a successful check should the body be parsed and queued for processing.

Block replayed requests

A valid signed request can be captured and sent again. Signature checks alone do not stop that, because the replayed request is genuine. Defences:

  • Timestamps: some providers include a signed timestamp. Stripe, for example, includes one in its signature header, and its libraries apply a default tolerance of five minutes between that timestamp and the current time. Reject requests outside your tolerance, and keep server clocks accurate.
  • Delivery IDs: many providers send a unique ID with each delivery. GitHub's webhook best practices suggest using the X-GitHub-Delivery header to ensure each delivery is unique per event, while noting that a redelivery you request keeps the same ID.
  • Event IDs: record processed event IDs and skip any you have seen before.

Handle duplicates safely

Duplicates are not always attacks. Providers retry when your endpoint is slow or returns an error, so the same event may legitimately arrive more than once. Your processing should be idempotent: handling an event twice should have the same effect as handling it once.

Common approaches:

  • Store each processed event ID with a unique constraint, and skip events already recorded
  • Use upserts keyed on a stable business identifier, such as an order number, rather than blind inserts
  • Check the current state before acting, for example not marking an invoice paid if it already is

Our guide to the webhook dead letter queue covers what to do with events that keep failing after retries.

Protect the secret

The signing secret is the key to the whole scheme:

  • Store it in a secrets manager or protected environment variable, never in source code
  • Use a different secret for each endpoint and environment
  • Restrict who can view or change it
  • Rotate it on a schedule and immediately if it may have leaked
  • During rotation, accept signatures from both the old and new secret for a short, defined window

Further layers of webhook security

Signatures are the core control, but other layers help:

  • HTTPS only, so payloads and signatures cannot be read in transit
  • IP allow-lists, where the provider publishes its sending addresses, as an extra filter rather than the only check
  • Rate limits on the endpoint, so a flood of requests cannot overwhelm downstream systems
  • Payload validation, checking that fields have expected types and values before use
  • Fetching the latest state from the provider's API for high-value events, rather than trusting every field in the payload

Respond fast, process later

Providers expect a quick response. GitHub's best practices, for example, say to respond within 10 seconds and suggest a queue so payloads can be processed asynchronously. Verify the signature, store or queue the event, return a success response, and do the real work in a background worker. Our guide to Zoho CRM webhooks explains how this applies to CRM events.

Test your webhook security

Controls that are never tested tend to fail quietly. Add a few checks to your test suite and release process:

  • A request with a missing signature header is rejected
  • A request with a valid signature but a modified body is rejected
  • A request with an old timestamp, where the provider supplies one, is rejected
  • The same event delivered twice is processed only once
  • Rotating the secret does not drop genuine events during the overlap window

Most providers also offer a way to send test events or redeliver past ones, which helps confirm the full path works end to end after each change.

Log carefully

Log enough to investigate problems: delivery ID, event type, verification result and processing outcome. Avoid logging full payloads that contain personal or financial data, and never log secrets or signature values in a way that could help an attacker.

Frequently Asked Questions

What is webhook security?

It is the set of controls that make sure incoming webhook requests come from the expected sender, have not been altered, are not replays of old requests and are processed only once.

How does webhook signature verification work?

The sender calculates an HMAC of the request body with a shared secret and sends it in a header. Your endpoint calculates the same value from the raw body and compares them safely.

Why does signature verification fail even with the right secret?

Usually because the body was parsed or modified before verification. The signature must be checked against the exact raw bytes received.

How do you stop replay attacks on webhooks?

Reject requests whose signed timestamps are too old where the provider supplies them, and record delivery or event IDs so repeats are skipped.

Secure your integration endpoints

We build and review webhook endpoints with signature checks, replay protection, queues and idempotent processing. See our CRM and API integration services, or talk to us about webhook security.

About the Author

Manav Sharma is the founder of Insyrge and a CRM solutions architect. He works across Zoho, Salesforce, HubSpot, Odoo and more, designing the CRM setups, integrations and automations that keep sales and operations teams working from the same data.

Need Help Implementing This in Your Business?

Our certified Zoho consultants and automation experts can help you design and deploy custom workflows tailored to your operations.

Book Free Consultation