Signature Verification
Every webhook delivery is signed with an HMAC-SHA256 signature computed over the raw request body, using your endpoint’s signing secret as the key.
⚠️
Always verify signatures before trusting an event. Never parse the body first.
Headers
| Header | Contents |
|---|---|
VaultsPay-Signature | t=<timestamp>,v1=<hex_signature> |
VaultsPay-Event-Id | The unique event ID — convenient for dedup. |
The t value is the Unix timestamp at which VaultsPay signed the payload. The v1 value is the HMAC signature.
Algorithm
signed_payload = `${t}.${raw_request_body}`
v1 = HMAC-SHA256(signing_secret, signed_payload).hex()You should:
- Split the header to extract
tandv1. - Compute the HMAC yourself using the raw body (do not re-encode).
- Compare in constant time using
crypto.timingSafeEqual(or your language’s equivalent). - Reject events with a
tolder than 5 minutes to avoid replay attacks.
Example
import crypto from 'crypto'
function verifySignature(rawBody, header, secret) {
if (!header) return false
const parts = Object.fromEntries(header.split(',').map(kv => kv.split('=')))
const { t, v1 } = parts
if (!t || !v1) return false
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex')
return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
}Rotating the signing secret
You can rotate an endpoint’s signing secret from the dashboard. There’s a 24-hour grace period where both the old and new secrets are valid, to let you deploy.