space.hooks.verify()
Check that a webhook your server received was sent by us, unchanged and recently.
Checks a delivery your server received from a destination: that it carries a valid Standard Webhooks signature made with the destination’s signing secret, over the body exactly as it was sent, and that it was sent no more than 5 minutes ago. Anyone who knows your URL can send it a request, so check every delivery before you act on it.
verify() runs on your own server and makes no request. It needs no API key and no project, only the destination’s signing secret.
space.hooks.verify(
request: Request | {
headers: Headers | Record<string, string | undefined>
body: string | Uint8Array
},
options: {
secret: string
toleranceSeconds?: number
now?: number
}
): Promise<VerifiedDelivery>Parameters
requestRequest | { headers, body }The delivery as your server received it. Pass a
Requestfrom any server that gives you one. From another server, pass itsheaders, asHeadersor an object of names and values in any case, and itsbodyas the raw bytes or text exactly as received.verify()reads thewebhook-id,webhook-timestamp, andwebhook-signatureheaders.options.secretstringThe destination’s signing secret, which starts with
whsec_. Read it withspace.hooks.destinations.signingSecret()and keep it in your server’s settings, such as an environment variable.options.toleranceSecondsintegerHow far the delivery’s
webhook-timestampmay be from now, in either direction, 1 to 86,400 seconds. 300, which is 5 minutes, when you leave it out. It stops someone from sending an old delivery again.options.nownumberThe time to check against, in milliseconds since 1 January 1970. The current time when you leave it out. Use it in tests.
Returns
A promise of the checked delivery.
idstringThe
webhook-idheader: the ID of the event. It is the same on every attempt and every time the delivery is sent again, so keep the IDs you have handled and skip a delivery you have already seen.timestampDateWhen this attempt was sent, from the
webhook-timestampheader.bodystringThe body, as UTF-8 text. When you pass a
Request,verify()reads its body, so read the body from here.json()<T = unknown>() => TParses
bodyas JSON. It throws aSyntaxErrorwhen the body is not JSON.
Errors
The promise rejects with a SpaceError.
| Code | When |
|---|---|
invalid_signature | A signing header is missing, webhook-timestamp is not a whole number of seconds, the delivery was sent more than toleranceSeconds ago or that far in the future, or no signature matches the secret and the body. status is 401 |
validation_failed | secret does not start with whsec_ followed by base64, or toleranceSeconds is not a whole number from 1 to 86,400 |
Answer an invalid_signature with 401 and do nothing else. When a real delivery fails the check, look for a secret from another destination, a secret that was reset, a body changed before the check, or a server clock that is more than 5 minutes out.
Examples
Any server that hands you a Request and takes a Response.
import { SpaceError, space } from "digitospace"
type Payment = {
reference: string
amount: number
msisdn: string
status: "COMPLETED" | "FAILED"
}
const handled = new Set<string>()
export async function POST(request: Request): Promise<Response> {
try {
const delivery = await space.hooks.verify(request, {
secret: process.env.WEBHOOK_SECRET ?? "",
})
if (handled.has(delivery.id)) return new Response(null, { status: 200 })
const payment = delivery.json<Payment>()
if (payment.status === "COMPLETED") {
console.log(`Order ${payment.reference} paid TZS ${payment.amount} from ${payment.msisdn}`)
}
handled.add(delivery.id)
return new Response(null, { status: 200 })
} catch (error) {
if (error instanceof SpaceError && error.code === "invalid_signature") {
return new Response(null, { status: 401 })
}
throw error
}
}Answer with a 2xx as soon as the delivery is checked and saved, and do slow work afterwards. An answer that takes longer than the destination’s timeoutSeconds, or any answer that is not 2xx, counts as a failed attempt and the delivery is tried again.
Last updated