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

# Event payloads

> The full body shape for each webhook event, with verification code.

## Verifying a webhook

Always verify the signature **before** parsing or trusting the body.

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

const app = express();
// IMPORTANT: get the raw body for HMAC verification.
app.use('/voucher-webhooks', express.raw({ type: 'application/json' }));

app.post('/voucher-webhooks', (req, res) => {
  const header = String(req.headers['x-signature'] || '');
  const parts = Object.fromEntries(
    header.split(',').map((p) => {
      const i = p.indexOf('=');
      return [p.slice(0, i), p.slice(i + 1)];
    }),
  );
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) {
    return res.status(400).send('stale');
  }

  const expected = createHmac('sha256', process.env.PARTNER_HMAC_SECRET!)
    .update(`${t}.${req.body.toString('utf8')}`)
    .digest('hex');
  const got = Buffer.from(parts.v1!, 'hex');
  const exp = Buffer.from(expected, 'hex');
  if (got.length !== exp.length || !timingSafeEqual(got, exp)) {
    return res.status(401).send('bad sig');
  }

  const event = JSON.parse(req.body.toString('utf8'));
  // ... handle event by event.type ...
  res.status(200).end();
});
```

<Note>
  **Voucher titles can contain inline markup.** Merchants may style the
  striking part of an offer with tiny tags — `{b}…{/b}` (bold), `{r}…{/r}`
  (red highlight), `{s}…{/s}` (strikethrough), which can nest
  (`{b}{r}RM38{/r}{/b}`). Any `voucherTitle` in a payload (or in `/me/*`
  responses) may include them. Strip the tags for a plain-text display
  (remove anything matching `\{/?[brs]\}`, case-insensitive) or render them
  if you want the emphasis.
</Note>

## `CLAIM_CREATED`

Emitted when the user adds a voucher to their wallet via
`POST /me/claims`.

```json theme={null}
{
  "id": "9f1a2c4d-5e6f-7891-a2b3-c4d5e6f78901",
  "type": "CLAIM_CREATED",
  "createdAt": "2026-05-12T08:14:22.501Z",
  "data": {
    "userId": "8d2a7f3b-1c4e-4b9a-9f55-d3a8e2c1b7d4",
    "externalUserId": "usr_demo_001",
    "claimId": "0c5a8b7f-9d22-4e1a-b6c1-7f8e92d4ab10",
    "voucherId": "3a7e8d12-...",
    "voucherTitle": "Free coffee",
    "merchantId": "..."
  }
}
```

| Field          | Type   | Description                                      |
| -------------- | ------ | ------------------------------------------------ |
| `claimId`      | UUID   | The new claim row. Unique per `(user, voucher)`. |
| `voucherId`    | UUID   |                                                  |
| `voucherTitle` | string | Snapshot at claim time.                          |
| `merchantId`   | UUID   |                                                  |

## `REDEMPTION_CREATED`

Emitted when the user redeems a voucher via `POST /me/redemptions`. **Both**
SUCCESS and FLAGGED redemptions fire this event.

```json theme={null}
{
  "id": "1d4e8f2c-3b6a-4d12-9e8f-c5b3a7d9e0f1",
  "type": "REDEMPTION_CREATED",
  "createdAt": "2026-05-12T08:14:22.501Z",
  "data": {
    "userId": "8d2a7f3b-...",
    "externalUserId": "usr_demo_001",
    "redemptionId": "...",
    "voucherId": "...",
    "voucherTitle": "Free coffee",
    "outletId": "...",
    "outletName": "Acme Cafe — Orchard",
    "status": "SUCCESS",
    "flagReason": null
  }
}
```

| Field          | Type                       | Description                                                                                                                                                            |
| -------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `redemptionId` | UUID                       | The new redemption row.                                                                                                                                                |
| `voucherTitle` | string                     | Snapshot at redemption time.                                                                                                                                           |
| `outletName`   | string                     | Snapshot at redemption time.                                                                                                                                           |
| `status`       | `"SUCCESS"` \| `"FLAGGED"` |                                                                                                                                                                        |
| `flagReason`   | string \| null             | `gps_unavailable` on a `FLAGGED` row, or `outlet_not_geolocated` — an audit tag that can also appear on a `SUCCESS` row when the outlet has no coordinates configured. |

<Note>
  The `code` dispensed to the user (if any) is **not** included in this
  payload — it's considered consumer-facing and only flows through the
  redemption response. If you need it server-side, fetch the redemption
  via the admin API.
</Note>

## `VOUCHER_PUBLISHED`

Emitted once per favoriting user when a merchant publishes a new voucher.
We fan out at publish time, so if 10k users have favorited the merchant,
10k events are enqueued.

```json theme={null}
{
  "id": "...",
  "type": "VOUCHER_PUBLISHED",
  "createdAt": "2026-05-12T08:14:22.501Z",
  "data": {
    "userId": "...",
    "externalUserId": "usr_demo_001",
    "voucherId": "...",
    "voucherTitle": "Buy one get one — May only",
    "merchantId": "...",
    "merchantName": "Acme Cafe",
    "valueType": "BOGO"
  }
}
```

| Field          | Type         | Description                                                                      |
| -------------- | ------------ | -------------------------------------------------------------------------------- |
| `voucherTitle` | string       | Snapshot at publish time.                                                        |
| `merchantName` | string       | Snapshot at publish time.                                                        |
| `valueType`    | enum \| null | One of `FREE_ITEM`, `PERCENT`, `FIXED`, `BOGO`. May be null. Informational only. |
