|
| 1 | +--- |
| 2 | +id: sbwmyo92 |
| 3 | +title: Webhooks |
| 4 | +description: Verify incoming Webiny webhook payloads using the Webiny SDK to ensure authenticity and integrity. |
| 5 | +--- |
| 6 | + |
| 7 | +import { Alert } from "@/components/Alert"; |
| 8 | + |
| 9 | +<Alert type="success" title="WHAT YOU'LL LEARN"> |
| 10 | + |
| 11 | +- Why you should verify webhook payloads before processing them |
| 12 | +- Which headers Webiny sends with every webhook delivery |
| 13 | +- How to use `createVerifyWebhookPayload` from `@webiny/sdk` to verify a payload |
| 14 | +- How to handle verification failures |
| 15 | + |
| 16 | +</Alert> |
| 17 | + |
| 18 | +## Overview |
| 19 | + |
| 20 | +When Webiny delivers a webhook, it signs the payload so that your receiving service can confirm two things: the request genuinely came from Webiny, and the payload was not tampered with in transit. |
| 21 | + |
| 22 | +Webiny follows the [Standard Webhooks](https://www.standardwebhooks.com) specification. Every delivery includes a set of headers that carry a message ID, a timestamp, and a cryptographic signature. The `@webiny/sdk` package provides a `createVerifyWebhookPayload` function that checks these headers against the raw request body and your signing secret. |
| 23 | + |
| 24 | +<Alert type="warning" title="Node.js only"> |
| 25 | + |
| 26 | +`@webiny/sdk/webhooks` relies on the `standardwebhooks` library, which is not compatible with browser environments. Only use it in server-side code. |
| 27 | + |
| 28 | +</Alert> |
| 29 | + |
| 30 | +## Webhook Headers |
| 31 | + |
| 32 | +Webiny sends three headers with every webhook delivery: |
| 33 | + |
| 34 | +| Header | Description | |
| 35 | +| ------------------- | ------------------------------------------------------------------------------------------------------- | |
| 36 | +| `webhook-id` | A unique identifier for the message. You can use this to detect duplicate deliveries. | |
| 37 | +| `webhook-timestamp` | A Unix timestamp (in seconds) of when the message was sent. | |
| 38 | +| `webhook-signature` | A Base64-encoded signature computed from the message ID, timestamp, and body using your signing secret. | |
| 39 | + |
| 40 | +Your receiving endpoint must forward all three headers — along with the **raw request body** — to the verification function. Parsing the body as JSON before verification will invalidate the signature. |
| 41 | + |
| 42 | +## Verifying a Payload |
| 43 | + |
| 44 | +Start by creating a verification function with your signing secret: |
| 45 | + |
| 46 | +```typescript |
| 47 | +import { createVerifyWebhookPayload } from "@webiny/sdk/webhooks"; |
| 48 | + |
| 49 | +const verifyWebhookPayload = createVerifyWebhookPayload(process.env.WEBHOOK_SECRET); |
| 50 | +``` |
| 51 | + |
| 52 | +`createVerifyWebhookPayload` accepts the signing secret and returns an async function you call for each incoming request. If the secret is `undefined`, the returned function throws immediately — this lets you catch misconfigured environments early. |
| 53 | + |
| 54 | +Call the returned function with the raw body and the three webhook headers: |
| 55 | + |
| 56 | +```typescript |
| 57 | +const result = await verifyWebhookPayload(rawBody, { |
| 58 | + "webhook-id": headers["webhook-id"], |
| 59 | + "webhook-timestamp": headers["webhook-timestamp"], |
| 60 | + "webhook-signature": headers["webhook-signature"] |
| 61 | +}); |
| 62 | +``` |
| 63 | + |
| 64 | +The function returns a [`Result`](/docs/developer-docs/6.x/core-concepts/basic/result) — check it before processing the payload: |
| 65 | + |
| 66 | +```typescript |
| 67 | +if (result.isFail()) { |
| 68 | + // Signature is invalid — reject the request. |
| 69 | + console.error("Webhook verification failed:", result.error); |
| 70 | + return; |
| 71 | +} |
| 72 | + |
| 73 | +const payload = result.value; |
| 74 | +``` |
| 75 | + |
| 76 | +## Error Handling |
| 77 | + |
| 78 | +Verification fails when: |
| 79 | + |
| 80 | +- **The signature does not match** — the payload was tampered with or the wrong secret was used. |
| 81 | +- **The timestamp is too old** — the `standardwebhooks` library rejects messages outside a tolerance window to prevent replay attacks. |
| 82 | +- **A required header is missing** — one or more of the three webhook headers was not included in the request. |
| 83 | + |
| 84 | +When verification fails, `result.isFail()` returns `true` and `result.error` contains the underlying error with a message describing the reason. Do not process the payload — return an appropriate HTTP error status to the caller. |
0 commit comments