Glossary
Webhook signature
How an endpoint anyone on the internet can POST to tells real deliveries from forged ones.
By Roger Campos · Last updated: September 2026
TL;DR
A webhook signature is a hash-based message authentication code (HMAC) that the sender computes over a webhook's body with a secret shared with the receiver, and sends in a header. The receiver recomputes it; if the two match, the request came from the sender and was not altered on the way.
Free plan, no credit card. 1,000 credits a month.
How it works
How signing works
A webhook URL has to accept unauthenticated POSTs from the internet, so anyone who learns it can send it data. Signing fixes that. Sender and receiver share a secret. For each delivery the sender computes HMAC-SHA256(secret, message) and sends the hex digest in a header; the receiver computes the same thing and compares.
Two details make it safe. The timestamp is part of the signed message, so an old delivery replayed later fails a freshness check. The comparison is constant-time (hmac.compare_digest, crypto.timingSafeEqual), so the time it takes leaks nothing about the expected value. And it must run over the raw body: parse the JSON and re-serialise it, and key order or whitespace changes the digest.
URLpipe
URLpipe's scheme
Signing is enabled per project under Settings → Webhook Signing. Each signed delivery carries X-URLpipe-Timestamp (Unix seconds) and X-URLpipe-Signature, of the form v1=<hex>, computed over <timestamp>.<raw body>. During a secret rotation the header carries two comma-separated signatures for 24 hours, so accept the delivery if any one matches.
import hashlib, hmac, os, time
from flask import request, abort
SECRET = os.environ["URLPIPE_WEBHOOK_SECRET"].encode()
def verify():
body = request.get_data() # raw bytes, not request.json
ts = request.headers.get("X-URLpipe-Timestamp", "")
header = request.headers.get("X-URLpipe-Signature", "")
if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
abort(401)
expected = "v1=" + hmac.new(SECRET, ts.encode() + b"." + body, hashlib.sha256).hexdigest()
if not any(hmac.compare_digest(s.strip(), expected) for s in header.split(",")):
abort(401)Why not simply put a secret token in the webhook URL? Because URLs end up in logs, proxies and error trackers, a leaked one works forever, and it proves nothing about the body. A signature is computed per delivery over the exact bytes sent, so a captured request cannot be altered, and cannot be replayed once its timestamp is outside your tolerance.
A delivery your endpoint rejects is retried with backoff, and any delivery can be re-sent by hand from the dashboard. The JavaScript and PHP versions are in the async & webhooks docs.
Try it
Webhook signature (HMAC), on a page you choose
Related terms
FAQ
Frequently asked questions
Why does my webhook signature not match?
What timestamp tolerance should I use?
Is HMAC the same as encryption?
See it on your own pages.
Free plan, no card. Confirm your email and your API key is live — you'll be making real requests in minutes.