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

# Webhook Verification

Every webhook Pagoramp sends to your configured `webhookUrl` is signed with your account's webhook secret. Verifying the signature confirms the request actually came from Pagoramp and wasn't forged or replayed.

## Headers

Each webhook request includes two headers:

* `X-Pagoramp-Signature`: a hex-encoded HMAC-SHA256 signature of the request.
* `X-Pagoramp-Timestamp`: the Unix timestamp (seconds) the request was signed at.

## Getting your webhook secret

Generate or view your webhook secret in the merchant dashboard under Settings > Webhooks. It's shown once at generation time - store it securely. You can regenerate it at any time (this invalidates the previous secret).

## Signature construction

```text theme={null}
signature = hex(hmac_sha256(secret, timestamp + "." + raw_request_body))
```

`raw_request_body` is the exact, unparsed JSON body of the request - verify against the raw bytes, not a re-serialized version of the parsed object (re-serializing can change key ordering or whitespace and break the signature).

## Verifying (Node.js example)

```js theme={null}
const crypto = require('crypto');

function verifyPagorampWebhook(rawBody, timestamp, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  // Constant-time comparison to avoid timing attacks.
  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(signature, 'hex'),
  );
}

// Express example
app.post('/webhooks/pagoramp', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-pagoramp-signature'];
  const timestamp = req.headers['x-pagoramp-timestamp'];
  const rawBody = req.body.toString('utf8');

  if (!verifyPagorampWebhook(rawBody, timestamp, signature, process.env.PAGORAMP_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(rawBody);
  // ... handle event
  res.status(200).send('ok');
});
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.