[relaybell docs › Browser SDK › Opt-in prompts](https://relaybell.com/docs/prompts) · [All docs as Markdown](https://relaybell.com/docs.md) · [Index for AI assistants](https://relaybell.com/llms.txt)

# Opt-in prompts

> Opt-in prompt styles, triggers, copy and A/B test (PUT /api/v1/prompt), and the preview link.

Prompt style, copy, trigger and colors are configured per project — in the dashboard under **Audience → Subscribe prompt** (with a live preview), or with `PUT /api/v1/prompt`. Styles: `slidein` (default; `card` is the same box), `bar`, `modal`, `native`. Triggers: `delay`, `load`, `click`, `scroll`, `exit` (desktop only). Add a privacy policy link in Settings → Privacy (it's the `privacyUrl` setting, not a prompt field) to show it on the prompt. Set a prompt `b` variant to A/B-test opt-in copy. Visitors who click the “no” button (or click outside a `modal`) aren't asked again for 7 days.

```bash
curl -X PUT https://relaybell.com/api/v1/prompt \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{ "prompt": { "type": "slidein", "trigger": "delay", "delaySeconds": 5,
        "title": "Stay in the loop", "message": "Get notified about offers.",
        "allowText": "Allow", "denyText": "No thanks",
        "position": "bottom-right", "accentColor": "#0B7C93" } }'
```

| Field | Values |
| --- | --- |
| `enabled` | Default `true`. `false` = never ask automatically (visitors can still subscribe via your own button + `subscribeNow()`). |
| `type` | `slidein` · `card` (another name for the same slide-in box) · `bar` · `modal` · `native` (browser prompt only — Safari and Firefox only allow it inside a click, so unless the trigger is `click` their visitors get the `slidein` box instead). |
| `trigger` | `delay` · `load` · `scroll` · `exit` · `click`. |
| `delaySeconds`, `scrollPercent` | For `delay` (0–600) and `scroll` (1–100). |
| `clickSelector` | CSS selector of your button, for the `click` trigger. |
| `title`, `message`, `allowText`, `denyText` | Prompt copy (longer text is cut to 120 / 300 / 40 / 40 characters). |
| `position` | `bottom-right` · `bottom-left` · `top-right` · `top-left` (slide-in), `top` · `bottom` (bar). |
| `accentColor` | Button color — must be a hex color like `#0B7C93`; anything else is rejected with `400 invalid_color`. |
| `iosInstallHint` | `false` hides the iPhone/iPad “Add to Home Screen” how-to. |
| `b` | Version B for an A/B test: `{ title, message, allowText }` (empty fields fall back to version A). |

`PUT /api/v1/prompt` takes the prompt as `{ "prompt": { … } }` (or the fields at the top level) and answers `{ ok: true, prompt }`. It accepts the fields listed above only — anything else is dropped, and an unknown `type`, `trigger` or `position` falls back to `slidein`, `delay` or the style's default — and it replaces the whole prompt, so send every field you want to keep (`GET /api/v1/prompt` returns the current one).

**Preview:** open any page of your site with `?rb_preview_prompt=1` (e.g. `https://yoursite.com/?rb_preview_prompt=1`). The SDK removes the parameter from the address bar and shows your *saved* prompt (version A, in a browser that supports push) straight away — ignoring `enabled`, the 7-day dismissal, the current permission and the trigger. Clicking “no” in preview doesn't record a dismissal. For `type: "native"` it shows the slide-in card instead, since the browser's own prompt can't be previewed. The dashboard's **See it on my website** link does exactly this.
