***

title: Webhook Verification
subtitle: Validate raw signed deliveries before updating local state
slug: integration-patterns/webhook-verification
-----------------------------------------------

Webhook handling should be intentionally strict: verify first, then trust the payload.

## Why this pattern

* the SDK checks HMAC signature and timestamp freshness
* the app preserves the exact raw request body for verification
* downstream handlers switch on a typed `eventType`
* local state changes happen only after signature validation succeeds

## Recommended shape

```typescript
import { readFile } from "node:fs/promises";

const rawBody = await readFile(rawBodyPath, "utf8");
const webhookUrl =
  `${config.publicBaseUrl ?? `http://127.0.0.1:${config.port}`}` +
  "/v1/webhooks/gwop";

const event = await webhooks.validateWebhook({
  rawBody,
  headers: {
    "x-gwop-signature": signature,
    "x-gwop-event-id": eventId,
    "x-gwop-event-type": eventType,
  },
  url: webhookUrl,
  method: "POST",
});

console.log(event.body.eventType);
console.log(event.body.data.invoiceId);
console.log(event.body.data.publicInvoiceId);
```

<Warning>
  Do not parse and re-stringify the JSON before verification. The signature is computed over the original raw request body bytes.
</Warning>

## Related pages

<CardGroup cols={2}>
  <Card title="Verify Signatures" icon="duotone signature" href="/webhooks/verify">
    See the operational webhook verification flow and the manual fallback
  </Card>

  <Card title="Webhook-Driven State" icon="duotone wave-pulse" href="/concepts/webhook-driven-state">
    Learn why signed delivery is the durable trigger for fulfillment
  </Card>

  <Card title="Webhooks" icon="duotone bell" href="/webhooks/overview">
    Review the event types and payload fields your handler should expect
  </Card>
</CardGroup>
