Guide

How to Secure Webhooks in Your SaaS Integrations

Quick answer

To secure webhooks, use HMAC-based signature verification, validate the payload on every request, enforce HTTPS, and implement replay protection with a unique ID and timestamp. Rotate secrets periodically and whitelist IP ranges where possible. Use a secret per integration to limit blast radius.

Why Webhooks Are a Security Risk

Webhooks are HTTP callbacks your system sends to a customer's endpoint when an event occurs. The risk is that an attacker can forge a webhook request to trigger unauthorized actions, or intercept a legitimate request to replay it. Common attacks include: forging a payment success event, replaying a user deletion request, or sending a malicious payload that exploits a vulnerability in the receiver. Without proper security, your customers' data and your own system are exposed.

Use HMAC Signatures to Verify Authenticity

The most common and effective method is to sign each webhook payload with a secret shared only between you and the receiver. Use HMAC-SHA256. The receiver computes the signature on their side and compares it to the one you sent.

Example: Your webhook payload is {"event":"user.created","id":"123"}. Your secret is whsec_abc123. Compute HMAC-SHA256(secret, payload) and send it in the header X-Webhook-Signature. The receiver does the same and checks equality.

Always include the raw payload in the signature computation. Do not include headers or metadata that could be altered. Use a constant-time comparison function (like hash_equals in PHP or hmac.compare in Node) to avoid timing attacks.

Prevent Replay Attacks with Unique IDs and Timestamps

An attacker can intercept a valid webhook and send it again to trigger the same action multiple times. To prevent this, include a unique event ID and a timestamp in the payload or headers.

Implementation: Send headers X-Webhook-Id (UUID) and X-Webhook-Timestamp (Unix epoch). The receiver should:

  • Reject requests where the timestamp is more than 5 minutes old.
  • Store processed webhook IDs for at least 24 hours and reject duplicates.

This way, even if a request is captured, it cannot be reused after the window expires, and the same ID cannot be processed twice.

Enforce HTTPS and Validate the Payload

Always send webhooks over HTTPS. Without TLS, the payload and signature are exposed to man-in-the-middle attacks. Set the Strict-Transport-Security header on your endpoints and reject any request that is not HTTPS.

Validate the incoming payload against a schema. Do not blindly trust the data. For example, if the webhook claims a payment of $100, check that the event type is valid, the amount is a positive number, and the user ID exists in your database. This prevents injection attacks and logical errors.

Whitelist IP Addresses When Possible

If you control the sender (your own service), publish a list of static IP ranges from which webhooks will originate. The receiver can then restrict incoming traffic to those IPs. This adds a layer of defense even if the secret is compromised.

For example, Stripe publishes their IP ranges: 54.187.174.0/24, 54.187.175.0/24. You can configure your firewall or application to only accept requests from those ranges. However, IP whitelisting alone is not sufficient because IPs can be spoofed (though unlikely over HTTPS). Combine it with signature verification.

Rotate Secrets and Use Per-Integration Secrets

Do not use the same secret for all customers. If one customer's secret leaks, all integrations are at risk. Generate a unique secret per integration (per customer or per webhook endpoint).

Provide a way for customers to rotate their secret. For example, in your dashboard, allow them to regenerate the secret. When a secret is rotated, keep the old one valid for a short overlap period (e.g., 24 hours) so the customer can update their receiver without missing events.

Store secrets securely. Use environment variables or a secrets manager. Never log or expose secrets in error messages.

Test Your Webhook Security with Automated Tools

After implementing these measures, test them. Try sending unsigned requests, modified payloads, expired timestamps, and duplicate IDs. Your receiver should reject them all.

Consider using an automated security testing tool to continuously validate your endpoints. For example, Kyro is an AI penetration tester for SaaS. You point it at your app URL, and it hunts for vulnerabilities like broken access control, injection, and SSRF. It can help you catch webhook security gaps before an attacker does.

Also, check your own webhook sending service. Ensure you are signing correctly, using HTTPS, and not leaking secrets in logs.

Find these bugs in your own app

Kyro runs an AI security hunter against your SaaS and emails you the moment it confirms a real, reproducible vulnerability.

Start a free scan

Frequently asked questions

What is the best way to verify a webhook signature?

Use HMAC-SHA256 with a shared secret. Include the raw payload in the signature, and compare using a constant-time function to prevent timing attacks.

Should I whitelist IPs for webhooks?

Yes, if your sender publishes static IP ranges. It adds defense in depth, but do not rely on it alone. Always combine with signature verification.

How often should I rotate webhook secrets?

Rotate secrets at least every 6 months, or immediately if you suspect a compromise. Provide customers a way to rotate on demand.

Can I use HTTPS alone to secure webhooks?

No. HTTPS encrypts the data in transit, but does not prevent an attacker from forging requests if they obtain your endpoint URL. Signature verification is essential.