> ## Documentation Index
> Fetch the complete documentation index at: https://docs.meum.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Verify signatures

> HMAC v1 webhook signature verification.

## Runtime verification (platform → merchant)

Outbound delivery  (platform webhook delivery):

```
signedPayload = timestamp + "." + rawBody
signature     = HMAC_SHA256(webhook_secret, signedPayload)
header value  = "v1=" + hex(signature)
```

Delivery headers set by the platform:

| Header                        | Value                                     |
| ----------------------------- | ----------------------------------------- |
| `Content-Type`                | `application/json`                        |
| `X-Stablecoin-Event`          | Event type string                         |
| `X-Stablecoin-Event-Id`       | Payload `id` field                        |
| `X-Stablecoin-Timestamp`      | Unix seconds (integer as string)          |
| `X-Stablecoin-Signature`      | `v1=<hex>`                                |
| `X-Stablecoin-Integration-Id` | Present when invoice has `integration_id` |

## Runtime verification (WooCommerce plugin receiver)

The WooCommerce plugin (WooCommerce plugin webhook security module) verifies:

1. `X-Stablecoin-Signature` and `X-Stablecoin-Timestamp` are present
2. Timestamp within **300 seconds** (`MAX_AGE_SECONDS`)
3. `X-Stablecoin-Integration-Id` matches local integration ID when both are set
4. Signature matches **v1** format: `v1=` + `hash_hmac('sha256', $timestamp . '.' . $raw_body, $secret)`
5. Legacy fallback accepted: `sha256=` + `hash_hmac('sha256', $raw_body, $secret)`

## Verification steps (custom integrations)

1. Read raw request body (before JSON parsing)
2. Extract `X-Stablecoin-Timestamp` and `X-Stablecoin-Signature`
3. Reject if timestamp is older than **300 seconds**
4. Compute `v1=` + HMAC-SHA256(secret, `${timestamp}.${rawBody}`)
5. Compare using constant-time comparison

## Example (Node.js)

```javascript theme={null}
const crypto = require("crypto");

function verifyWebhook(rawBody, headers, secret) {
  const timestamp = headers["x-stablecoin-timestamp"];
  const signature = headers["x-stablecoin-signature"];
  if (!timestamp || !signature) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > 300) return false;
  const signed = `${timestamp}.${rawBody}`;
  const expected = "v1=" + crypto.createHmac("sha256", secret).update(signed).digest("hex");
  try {
    return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  } catch {
    return false;
  }
}
```

## Related pages

* [Webhook security](/security/webhook-security)
* [WooCommerce webhook verification](/woocommerce/webhook-verification)
