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

# Verify webhook signatures

> Authenticate Biqli webhook requests with the exact raw JSON body.

Biqli signs every request with HMAC-SHA256 using the endpoint's `whsec_...` secret.

```text theme={null}
Biqli-Signature = lowercase_hex(HMAC_SHA256(raw_request_body, signing_secret))
```

Verify the signature **before** parsing or processing the JSON. JSON parsing and re-encoding can change whitespace or escaping and produce a different digest.

## Node.js example

```ts theme={null}
import {createHmac, timingSafeEqual} from 'node:crypto';

export async function POST(request: Request) {
  const rawBody = await request.text();
  const received = request.headers.get('biqli-signature') ?? '';
  const expected = createHmac(
    'sha256',
    process.env.BIQLI_WEBHOOK_SECRET!,
  ).update(rawBody).digest('hex');

  const validFormat = /^[a-f0-9]{64}$/.test(received);
  const valid = validFormat && timingSafeEqual(
    Buffer.from(received, 'hex'),
    Buffer.from(expected, 'hex'),
  );

  if (!valid) {
    return new Response('Invalid signature', {status: 401});
  }

  const event = JSON.parse(rawBody);
  await safelyAcceptOnce(event.id, event);
  return new Response('Accepted', {status: 200});
}
```

## PHP example

```php theme={null}
<?php

$rawBody = file_get_contents('php://input');
$received = $_SERVER['HTTP_BIQLI_SIGNATURE'] ?? '';
$expected = hash_hmac(
    'sha256',
    $rawBody,
    $_ENV['BIQLI_WEBHOOK_SECRET'],
);

if (!preg_match('/^[a-f0-9]{64}$/', $received)
    || !hash_equals($expected, $received)) {
    http_response_code(401);
    exit('Invalid signature');
}

$event = json_decode($rawBody, true, flags: JSON_THROW_ON_ERROR);
safelyAcceptOnce($event['id'], $event);
http_response_code(200);
```

## Prevent duplicate processing

Signature verification proves the body was signed with your endpoint secret. It does not make delivery exactly once.

Store the envelope `id` in a table with a unique constraint before applying side effects. If the same ID arrives again, return `2xx` without repeating the work.

## Header checks

After signature verification, you may confirm:

* `Biqli-Event-Id` equals the body `id`.
* `Biqli-Event` equals the body `event`.
* `Content-Type` is `application/json`.

Use the signed body as the authoritative event content.

<Warning>
  Never expose the signing secret in a browser, response, client bundle, URL, or application log.
</Warning>
