> ## 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.

# Quickstart

> End-to-end worked example: provision a user, mint a JWT, open the webview, receive the webhook.

This walks through the whole flow against a local dev install (`localhost:4000`).
Adapt the host and secret for staging / production.

<Note>
  All snippets assume `bash`, `curl`, `openssl`, `python3`, and Node ≥ 20.
</Note>

## 0. Sandbox credentials

Ask us for a test partner; for local development you can use the seeded `acme`
partner whose credentials are printed at the bottom of `npm run db:seed`.
A worked set looks like:

```
slug:        acme
hmac secret: dev_acme_secret_change_me
issuer:      https://acme.example.test
private key: .secrets/acme.private.pem
```

## 1. Provision a user from your backend

<CodeGroup>
  ```sh curl theme={null}
  SECRET='dev_acme_secret_change_me'
  BODY='{"externalUserId":"usr_demo_001","email":"demo@acme.example.test","displayName":"Demo User","countryCode":"SG"}'
  T=$(date +%s)
  V1=$(printf '%s' "${T}.${BODY}" | openssl dgst -sha256 -hmac "$SECRET" -binary | xxd -p -c 256)

  curl -sS -X POST http://localhost:4000/integrations/users \
    -H 'content-type: application/json' \
    -H 'x-partner-slug: acme' \
    -H "x-signature: t=${T},v1=${V1}" \
    -d "$BODY"
  ```

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

  const SECRET = process.env.PARTNER_HMAC_SECRET!;
  const body = JSON.stringify({
    externalUserId: 'usr_demo_001',
    email: 'demo@acme.example.test',
    displayName: 'Demo User',
    countryCode: 'SG',
  });
  const t = Math.floor(Date.now() / 1000);
  const v1 = createHmac('sha256', SECRET).update(`${t}.${body}`).digest('hex');

  const res = await fetch('http://localhost:4000/integrations/users', {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'x-partner-slug': 'acme',
      'x-signature': `t=${t},v1=${v1}`,
    },
    body,
  });
  console.log(await res.json());
  ```
</CodeGroup>

Expected response:

```json theme={null}
{
  "userId": "8d2a7f3b-1c4e-4b9a-9f55-d3a8e2c1b7d4",
  "created": true
}
```

Save `userId` if you want to look the user up in our admin panel — it's not
required by any subsequent call.

## 2. Mint a JWT for the user

Save this script as `mint-jwt.mjs`:

```js mint-jwt.mjs theme={null}
import { SignJWT, importPKCS8 } from 'jose';
import { readFileSync } from 'node:fs';

const pem = readFileSync('.secrets/acme.private.pem', 'utf8');
const key = await importPKCS8(pem, 'RS256');

const jwt = await new SignJWT({
  email: 'demo@acme.example.test',
  displayName: 'Demo User',
})
  .setProtectedHeader({ alg: 'RS256' })
  .setIssuer('https://acme.example.test')
  .setSubject('usr_demo_001')
  .setIssuedAt()
  .setExpirationTime('1h')
  .sign(key);

process.stdout.write(jwt);
```

Run it:

```sh theme={null}
JWT=$(node mint-jwt.mjs)
echo "$JWT"
```

## 3. Bootstrap the consumer session

```sh theme={null}
curl -sS http://localhost:4000/me \
  -H "Authorization: Bearer $JWT"
```

Expected:

```json theme={null}
{
  "id": "8d2a7f3b-1c4e-4b9a-9f55-d3a8e2c1b7d4",
  "email": "demo@acme.example.test",
  "displayName": "Demo User",
  "partner": {
    "id": "...",
    "name": "Acme",
    "slug": "acme",
    "displayName": "Acme Rewards",
    "logoPath": "uploads/partners/acme-logo.png"
  }
}
```

## 4. Open the webview

In your native app, load:

```
http://localhost:5173/consumer#token=<paste-JWT-here>
```

(Production: `https://<your-host>/consumer#token=…` — the CMS / consumer SPA
ships from the same origin as the API.)

The webview will call `/me`, render the catalog, and let the user claim and
redeem vouchers.

## 5. Receive the redemption webhook

When the user redeems a voucher, your `webhookUrl` receives a POST. See
[Webhook payloads](/webhooks/payloads) for the body shape and a verification
snippet.

## 6. Anonymise the user

For a deletion / GDPR-style request:

```sh theme={null}
T=$(date +%s)
# Empty body — sign empty string.
V1=$(printf '%s' "${T}." | openssl dgst -sha256 -hmac "$SECRET" -binary | xxd -p -c 256)

curl -sS -X DELETE \
  "http://localhost:4000/integrations/users/usr_demo_001" \
  -H 'x-partner-slug: acme' \
  -H "x-signature: t=${T},v1=${V1}" \
  -w 'HTTP %{http_code}\n'
```

Expected: `HTTP 204`. The user's PII is cleared; their redemption history is
preserved. You can revive the row at any time with another `POST /integrations/users`.

## Next steps

<CardGroup cols={2}>
  <Card title="JWT auth" icon="key" href="/authentication/jwt">
    Required claims, JWKS rotation, library examples.
  </Card>

  <Card title="HMAC auth" icon="signature" href="/authentication/hmac">
    Algorithm, rotation, signing snippets.
  </Card>

  <Card title="Webhook delivery" icon="repeat" href="/webhooks/delivery">
    Retry schedule, idempotency, receiver checklist.
  </Card>

  <Card title="Errors reference" icon="circle-exclamation" href="/reference/errors">
    Every error code, what it means, what to do.
  </Card>
</CardGroup>
