What a webhook secret is
A webhook is an outbound HTTP request a service sends to a URL you registered, telling your backend that something happened. The problem is that the URL is reachable by anyone on the internet, so your backend cannot trust a request just because it arrived — it has to prove the request really came from the service it expects. The webhook secret is what makes that proof possible. It is a high-entropy value generated when you set up the endpoint and shared once between the two sides; from then on, each side keeps its own copy and the secret itself never travels on the wire.
The secret is used through HMAC, a keyed hash. To send an event, the service computes an HMAC signature over the exact bytes of the event body using the secret, and attaches that signature to the request, usually in a header. To accept the event, your backend recomputes the same HMAC over the bytes it actually received, using its copy of the secret, and compares its result to the signature on the request. Because producing or checking a valid signature requires the key, a third party who can see the request in transit still cannot forge one — they would need the secret, which they do not have.
A webhook secret is therefore a symmetric credential: the same value is used to sign and to verify, so both holders can do both operations. That is different from a public/private key pair, where signing and verifying use different keys. It also means the secret has to stay confidential on both ends — anyone who learns it can mint events your backend will accept as genuine. The secret authenticates and protects the integrity of each event; it does not encrypt the event, which stays readable to anyone who intercepts it.
Why it matters for crypto payments
Payment events are exactly the kind of message an attacker wants to forge. A fake "the invoice was paid" notification can trick a backend into shipping goods or unlocking a service that was never paid for. Because the endpoint URL is public the moment you register it, anyone can POST plausible-looking JSON to it. The webhook secret is what lets your backend reject those forgeries: only the payment provider holds the same secret, so only the provider can produce a request whose signature verifies. Verification, not the appearance of the payload, is the deciding test on every incoming event.
How you handle the secret directly determines whether that protection holds. Store it server-side in a secret manager, never ship it to a browser or mobile app, and keep it out of source control, logs, and error reports. Verify the signature over the raw request body before any framework re-encodes it and before you trust a single field, and compare values in constant time so the comparison's duration does not leak how close a guess was. If the secret is ever exposed — committed by accident, printed in a log, leaked from a third party — rotate it, because from that moment anyone holding it can forge events your backend will accept.
It helps to keep secrets scoped and distinct. A secret tied to one endpoint should not be reused as an API key or a database password, and separate environments should use separate secrets so a leak in testing cannot be used against production. The narrower the blast radius of any single secret, the less a single exposure can do.
Webhook secrets on halfin
halfin signs every webhook it sends with HMAC, using the signing secret tied to your endpoint. Each event — invoice.confirming, invoice.paid, invoice.overpaid, invoice.underpaid, invoice.expired, invoice.late_deposit, invoice.deposit_reversed, balance.credited, payout.completed, payout.failed — is a JSON envelope whose signature travels in a request header. Your handler recomputes the HMAC over the exact raw request body with your copy of the secret, compares it to the header in constant time, and only then deserializes and acts on the event. A request whose signature does not verify is treated as hostile: return a 4xx and do nothing — never fulfil an order, credit a balance, or release a payout on the strength of an unverified payload.
You configure the endpoint and its signing secret from the dashboard or the API, and you store that secret the way you store any credential that can move value. If you suspect it has been exposed, rotate it. The webhook secret and the signature it produces are a pair: the signature is the check your handler runs on each request, and the secret is the knowledge that makes the check meaningful — guard the secret and the check stays trustworthy; leak it and the check is worthless.
- A webhook secret is a shared key used to sign and verify a webhook's HMAC signature, proving the event came from the sender and was not altered.
- It is symmetric — the same value signs and verifies — so it must stay confidential on both ends; anyone who holds it can forge events.
- Store it server-side in a secret manager, keep it out of browsers, logs, and source control, and rotate it if it is ever exposed.
- halfin signs every webhook with this secret; verify the signature over the raw body in constant time before acting, and reject anything that fails.