# Consent forwarding



Consent forwarding sends every saved consent record from OptinStack to an **HTTPS endpoint you control**. Use it for:

* A customer-owned long-term archive (beyond OptinStack's standard 12-month retention)
* Warehouse ingestion, custom reporting, or internal audit pipelines
* A self-serve operational copy independent of [fair-use allowances](/fair-use-and-enterprise)

OptinStack still stores each record normally. Forwarding runs in the background so a slow or failing endpoint **never blocks** the live Consent banner.

<Callout type="warn" title="Authenticated forwarding is recommended">
  Your endpoint receives consent choices and related identifiers. Prefer **Bearer token** authentication so only
  OptinStack (with your secret) can write to that API. **No auth** is available for quick tests, but unauthenticated
  public endpoints can be abused and are not recommended for production.
</Callout>

## Quick setup [#quick-setup]

1. Create a secure token (see below).
2. Build an HTTPS receiver that verifies `Authorization: Bearer <token>`.
3. In the dashboard: **Settings → Consent → Records & archive**.
4. Enable **Consent forwarding**, enter the HTTPS URL, choose **Bearer token**, paste the token, then **Save**.
5. Click **Test endpoint** to send a synthetic consent event and confirm a 2xx response.

Bearer tokens are **write-only** in OptinStack. After save, the dashboard only shows that a token is configured (not the secret). To rotate, paste a new token and save again.

## Generate a secure token [#generate-a-secure-token]

Generate a secure shared secret using a **cryptographically secure random generator**. The secret should contain **at least 32 random bytes**. Store it only in your server environment and OptinStack settings—never in client-side code or git.

Examples:

**OpenSSL (recommended):**

```bash
openssl rand -base64 32
```

**Node.js:**

```bash
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
```

**Python:**

```bash
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
```

Example workflow:

```bash
# 1. Generate at least 32 random bytes (encoded for pasting)
TOKEN=$(openssl rand -base64 32)
echo "$TOKEN"   # paste into OptinStack Settings once

# 2. Store on your receiver (never commit)
export OPTINSTACK_FORWARDING_TOKEN="$TOKEN"
```

## Request format [#request-format]

OptinStack sends `POST` with JSON.

```http
POST /api/optinstack/consents HTTP/1.1
Host: your-api.example.com
Content-Type: application/json
User-Agent: OptinStack-Consent-Forwarder/1.0
X-OptinStack-Project-Id: proj_123
X-OptinStack-Consent-Id: consent_123
Authorization: Bearer YOUR_TOKEN
```

| Header                    | Purpose                                                               |
| ------------------------- | --------------------------------------------------------------------- |
| `Authorization`           | Present when you selected **Bearer token**. Format: `Bearer <token>`. |
| `X-OptinStack-Project-Id` | Project that produced the consent.                                    |
| `X-OptinStack-Consent-Id` | Consent record identifier.                                            |
| `User-Agent`              | Always `OptinStack-Consent-Forwarder/1.0`.                            |

The JSON body is the consent record, including flat category booleans such as `analytics`, `marketing`, and `preferences` (see product consent sync shape). Design your handler to accept additional fields over time.

## Verify the bearer token (example) [#verify-the-bearer-token-example]

Always compare against a **server-side** secret. Reject missing or wrong tokens with `401`.

```ts
import express from 'express';

const app = express();
app.use(express.json({ limit: '256kb' }));

app.post('/api/optinstack/consents', (req, res) => {
  const expected = process.env.OPTINSTACK_FORWARDING_TOKEN;
  const auth = req.header('authorization') ?? '';

  if (!expected || auth !== `Bearer ${expected}`) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  const projectId = req.header('x-optinstack-project-id');
  const consentId = req.header('x-optinstack-consent-id');

  // Persist, enqueue, or warehouse-write here
  console.log('OptinStack consent', {
    projectId,
    consentId,
    action: req.body.action,
    analytics: req.body.analytics,
    marketing: req.body.marketing,
    preferences: req.body.preferences,
  });

  // Prefer 2xx so OptinStack marks delivery successful
  return res.status(204).send();
});

app.listen(3000);
```

### Checklist for production [#checklist-for-production]

* [ ] HTTPS only (OptinStack requires a secure endpoint URL)
* [ ] **Bearer token** enabled in OptinStack (not “No auth”)
* [ ] Token generated with a CSPRNG (at least 32 random bytes; not a short password)
* [ ] Token stored only in secrets manager / env on your receiver
* [ ] Endpoint rejects requests without a valid `Authorization` header
* [ ] **Test endpoint** succeeds from Settings after deploy
* [ ] You monitor failures (Settings shows last failure reason when delivery fails)

## Delivery behavior [#delivery-behavior]

* Project-level forwarding (Settings) is preferred. Older request-level `custom_endpoint` forwarding remains a fallback when no project endpoint is set.
* Non-2xx responses or network errors are recorded on the project (last failure reason). Consent **ingest still succeeds** for the visitor.
* Re-test after fixing your API; OptinStack does not re-send historical records automatically.

## Related [#related]

* [Consent record retention](/consent-retention) — what OptinStack keeps for 12 months
* [Fair-use and Enterprise](/fair-use-and-enterprise) — traffic allowances and custom volume
