Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add info for webhook signature validation #3054

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,69 @@ We provide an <LinkOut href="https://github.com/interledger/rafiki/blob/main/pac

Additionally, the [local playground](/integration/playground/overview) contains example payloads in the <LinkOut href="https://github.com/interledger/rafiki/tree/main/bruno/collections/Rafiki/Sample%20Webhook%20Events">Bruno collection</LinkOut> that can be used to test your webhook service integration.

## Verify webhook signatures

To protect your endpoint from unauthorized or spoofed requests, Rafiki supports an optional, but highly recommended, webhook signature verification process. By enabling signature verification, you can ensure that webhook requests are genuinely from Rafiki.

Each webhook request includes a `Rafiki-Signature` header with a timestamp, version, and signature digest. If you instance is configured with both the `SIGNATURE_SECRET` (to generate the signature) and the `SIGNATURE_VERSION` (to set the version, defaults to v1) environment variables, you can verify the authenticity of each webhook request using the steps below.

### Extract the timestamp and signature from the header

The `Rafiki-Signature` header in each webhook request has the following format:

<CodeBlock title="Rafiki-Signature header">

```
Rafiki-Signature: t=<timestamp>, v<version>=<signature_digest>
```

</CodeBlock>

- `t=<timestamp>`: The UNIX timestamp (in seconds) when the signature was generated.
- `v<version>=<digest>`: The versioned HMAC SHA-256 signature digest. The default version is `v1`.

### Prepare the signed payload string

To recreate the signed payload string, concatenate the following.
- The timestamp extracted from the header
- A period (.) character
- The actual JSON payload from the request body, containing the `id`, `type`, and `data` attributes

This string format is essential for accurate signature validation.

### Generate the expected signature

Use HMAC SHA-256 with the `SIGNATURE_SECRET` environment variable as the key and the signed payload string as the message.

### Compare the signatures

Finally, compare the signature in the header to the expected signature you generated. For security, use a constant-time comparison function to prevent timing attacks.

### Example
Below is an example in JavaScript to verify Rafiki's webhook signature:

<CodeBlock title="Verify webhook signature example">

```js
function verifyWebhookSignature(request: Request): boolean {
const signatureParts = request.headers['Rafiki-Signature'].split(', ')
const timestamp = signatureParts[0].split('=')[1]
const signatureVersionAndDigest = signatureParts[1].split('=')
const signatureVersion = signatureVersionAndDigest[0].replace('v', '')
const signatureDigest = signatureVersionAndDigest[1]
if (signatureVersion !== config['SIGNATURE_VERSION']) {
return false
}
const payload = `${timestamp}.${canonicalize(request.body)}`
const hmac = createHmac('sha256', config['SIGNATURE_SECRET'])
hmac.update(payload)
const digest = hmac.digest('hex')
return digest === signatureDigest
}
```

</CodeBlock>

## Event handling

### Asynchronous handling
Expand Down
Loading