Integrate Boostar
Intelligent App Store / Play Store review prompts, driven by configurable remote rules. Stop asking for reviews at random — ask when a user is in a good moment, defined by rules you control and evaluated on-device.
This guide takes you from the credentials Boostar gave you to a verified, firing review prompt in your app.
Recommended for mobile apps.
Native iOS/Android, server, or smoke tests.
A checklist to prove it before you ship.
What you need from Boostar
Boostar provisions your app and issues your credentials — there's nothing to self-host. You should have received:
| You need | Looks like | Notes |
|---|---|---|
| SDK key | bk_… (~43 chars) | One per app (iOS and Android are separate apps → separate keys). Secret — treat like a password. |
| API base URL | https://api.boostar.app | The SDK's default. You only set it explicitly to override. |
Don't have these yet? Contact Boostar to get your app registered and a key issued. Keys are shown once at issue time and stored only as a hash — if you lose one, Boostar revokes it and issues a new one.
track() the agreed trigger event and the prompt fires.
How it works (60 seconds)
-
The SDK registers an anonymous device (
anonId) and pulls down your app's rules (cached locally). - Your app calls
track('some_event')as users do things. -
On each tracked event, the SDK evaluates the cached rules on-device
(works offline). If a rule matches and its cooldown has elapsed, it fires the
native review prompt (
SKStoreReviewControlleron iOS / In-App Review on Android). - Events and prompt telemetry flow back to Boostar so rules can be tuned.
identify() is optional and only links an anonymous device
to your own user id.
Core concepts
| Concept | What it is |
|---|---|
| App | One platform build (iOS or Android). Has a bundleId, a platform, and one or more SDK keys. |
| SDK key | A bk_… secret sent in the x-boostar-key header. Scoped to a single app. |
| Device | An anonymous install, keyed by anonId (SDK-generated, persisted on device). |
| User | Optional. Your externalId linked to a device via identify(). |
| Event | A named behavioral signal (checkout_completed, app_opened, …) with optional JSON properties. |
| Rule | Remote config: a trigger event + conditions + prompt copy. Managed by Boostar; evaluated client-side. |
Rule conditions
Rules are evaluated by the SDK on-device. The conditions your rule may use:
| Field | Type | Meaning |
|---|---|---|
minCount | number | Require ≥ N total occurrences of the trigger event before firing. |
requireMinSessionCount | number | Require ≥ N app sessions. |
requireMinFeedbackCount | number | Require ≥ N feedback responses (any sentiment). An engagement gate, not a sentiment gate. |
propertyFilters | object | Require event properties to exactly match these key/values. |
cooldownDays | number | Min days between firings of this rule. |
extendCooldownOnNegativeSentiment | number | If the most recent feedback was negative and recent, delay the prompt by this many days. A timing modifier — every user still becomes eligible eventually. |
askFeedback() modulates timing only,
never who gets asked. Suppressing the public store prompt based on a
negative response is review gating and violates App Store / Play policy. The rule engine
deliberately offers no such switch.
Path A — React Native / Expo SDK
Install
# from your app
pnpm add @boostar-sdk/sdk-react-native
# peer deps (if not already present)
pnpm add expo expo-modules-core @react-native-async-storage/async-storage Requires Expo ≥ 52, React Native ≥ 0.74, React ≥ 18.2. The SDK includes a native module (iOS Swift / Android Kotlin) for the system review prompt, so test on a dev build / EAS build — the native prompt won't appear in Expo Go.
Wrap your app
import { BoostarProvider } from '@boostar-sdk/sdk-react-native';
export default function App() {
return (
<BoostarProvider
apiKey="bk_…" // your SDK key (use an env var, don't hardcode for release)
debug // verbose logs — turn OFF for production
>
<YourApp />
</BoostarProvider>
);
}
You normally don't set apiUrl — it defaults to
https://api.boostar.app. Only override it if Boostar gave you a different
endpoint.
BoostarProvider:
- calls
Boostar.initialize(...)on mount, - auto-mounts
<BoostarFeedbackHost />(soaskFeedbackworks — passmountFeedbackHost={false}to place it yourself), - exposes
isReadyvia theuseBoostar()hook, - flushes the event queue on unmount.
Track events & fire prompts
import { useBoostar } from '@boostar-sdk/sdk-react-native';
function CheckoutScreen() {
const { isReady, track, identify } = useBoostar();
async function onPurchase() {
// optional: link this device to your own user id
await identify('customer_42', { plan: 'pro', signupDays: 18 });
// track the behavioral event — may fire a review prompt if a rule matches
await track('checkout_completed', { amount: 49.99, currency: 'USD' });
}
} Outside React (push handlers, services, background tasks), use the singleton directly:
import Boostar from '@boostar-sdk/sdk-react-native';
await Boostar.track('subscription_renewed'); Full API surface
| Method | Purpose |
|---|---|
Boostar.initialize(config) | Start the SDK. Idempotent for the same apiKey. |
Boostar.identify(externalId, traits?) | Link the anon device to your user id (+ optional traits). |
Boostar.track(name, properties?) | Record an event; evaluates rules and may fire a prompt. |
Boostar.requestReview() | Manually fire the native prompt, bypassing rules (still subject to OS frequency caps). Returns { requested, reason? }. |
Boostar.askFeedback(options) | Show a lightweight sentiment prompt (👍/👎 or 😊😐😞). Returns { dismissed, sentiment? }. Requires the feedback host mounted. |
Boostar.flush() | Force-push the pending event queue. |
Boostar.reset() | Wipe local state (anonId, counters, prompt history). Handy to re-test. |
const res = await Boostar.requestReview();
// { requested: true } | { requested: false, reason: 'cooldown' | 'no-matching-rule' | ... }
const fb = await Boostar.askFeedback({
question: 'How was checkout?',
type: 'thumbs', // or 'emoji'
context: 'checkout_completed',
cooldownDays: 7,
});
// { dismissed: false, sentiment: 'positive' } | { dismissed: true, reason: 'cooldown' | 'no-host' | ... } Path B — Raw HTTP API
Use this to smoke-test your key before touching app code, or to integrate a non-RN client.
Base URL: https://api.boostar.app.
x-boostar-key: bk_…. Missing/invalid →
401.
POST /api/sdk/init — register a device
Call once per app start. Idempotent on (app, anonId).
curl -s -X POST https://api.boostar.app/api/sdk/init \
-H "Content-Type: application/json" \
-H "x-boostar-key: $BOOSTAR_KEY" \
-d '{
"anonId": "test-device-001",
"osVersion": "17.5",
"appVersion": "1.0.0",
"locale": "en-US",
"timezone": "America/Montevideo"
}'
# → { "deviceId": "..." } | Field | Required | Notes |
|---|---|---|
anonId | ✅ | 8–64 chars. Stable per install; you generate it. |
osVersion appVersion | – | ≤ 32 chars. |
locale | – | 2–10 chars. |
timezone | – | ≤ 64 chars. |
POST /api/sdk/identify — link a user
Requires a prior init for that anonId (else 404).
curl -s -X POST https://api.boostar.app/api/sdk/identify \
-H "Content-Type: application/json" \
-H "x-boostar-key: $BOOSTAR_KEY" \
-d '{
"anonId": "test-device-001",
"externalId": "customer_42",
"email": "user@example.com",
"traits": { "plan": "pro", "signupDays": 18 }
}'
# → { "userId": "...", "deviceId": "..." } externalId is required (1–128 chars). email must be valid if
present. traits is free-form JSON.
POST /api/sdk/events — ingest events
Batch of 1–100 events. Returns 202 Accepted. Requires a prior init
(else 404).
curl -s -X POST https://api.boostar.app/api/sdk/events \
-H "Content-Type: application/json" \
-H "x-boostar-key: $BOOSTAR_KEY" \
-d '{
"anonId": "test-device-001",
"events": [
{ "name": "app_opened", "occurredAt": "2026-06-20T12:00:00.000Z" },
{ "name": "checkout_completed", "occurredAt": "2026-06-20T12:05:00.000Z",
"properties": { "amount": 49.99, "currency": "USD" } }
]
}'
# → { "ingested": 2 } | Per-event field | Required | Notes |
|---|---|---|
name | ✅ | 1–80 chars. |
occurredAt | ✅ | ISO 8601 timestamp. |
properties | – | Free-form JSON object. |
GET /api/sdk/config — fetch your rules
The fastest way to confirm your key is live and your rule exists:
curl -s https://api.boostar.app/api/sdk/config \
-H "x-boostar-key: $BOOSTAR_KEY"
# → { "appId": "...", "rules": [ { "id", "trigger", "conditions", "prompt", "cooldownDays", "priority" }, ... ] }
Only enabled rules are returned, sorted by descending priority.
Matching happens on the client — this endpoint just serves config.
Test your integration
Work through this top to bottom. Each step has an expected result so you know exactly where a problem is.
Step 1 — Confirm your key and rule (no app needed)
export BOOSTAR_KEY="bk_…"
curl -s https://api.boostar.app/api/sdk/config -H "x-boostar-key: $BOOSTAR_KEY" - ✅ Expected: JSON with your
appIdand at least one rule. Note the rule'striggerandconditions— that's what you'll satisfy to make the prompt fire. - ❌
401→ wrong/missing key. ❌ emptyrules→ ask Boostar to configure your first rule.
Step 2 — Confirm ingestion end-to-end (no app needed)
Run init then events from the curl examples above with the same anonId.
- ✅ Expected:
initreturns adeviceId;eventsreturns{ "ingested": N }. - ❌
404on events → you skippedinitfor thatanonId.
Step 3 — Wire the SDK into a build
Add BoostarProvider with debug enabled, on a
dev/EAS build (not Expo Go). On launch, the debug logs should show the SDK
starting and an anonId: [Boostar] started with anonId <…>
- ❌ No logs / "called before initialize" → the provider isn't mounted above the code calling
Boostar.*.
Step 4 — Force a prompt (fastest visual check)
requestReview() bypasses the rule engine, so it's the quickest way to confirm the native prompt is wired:
const res = await Boostar.requestReview();
console.log(res); // expect { requested: true } - ✅ iOS Simulator / first run: the system rating sheet appears.
requested: true
— Apple and Google cap how often it shows (≈3×/year on iOS) and never tell the app.
requested: true means your wiring is correct; the OS decides
display. This is expected, not a bug.
Step 5 — Fire via a rule (the real path)
Satisfy your rule's conditions, then track() its trigger. For the typical
onboarding rule (trigger: checkout_completed, needs ≥5 feedback interactions
first):
// 1. satisfy requireMinFeedbackCount (call 5×, cooldownDays:0 so testing isn't throttled)
for (let i = 0; i < 5; i++) {
await Boostar.askFeedback({ question: 'How was it?', type: 'thumbs', context: 'demo', cooldownDays: 0 });
}
// 2. fire the trigger
await Boostar.track('checkout_completed', { amount: 49.99, currency: 'USD' }); - ✅ Expected (debug logs):
firing rule <id> (trigger: checkout_completed), then the native prompt (subject to Step 4's OS caveat). - ❌
no prompt: <reason>in logs tells you which condition failed:
| Log reason | Fix |
|---|---|
no-matching-rule | Trigger name doesn't match any rule. Check Step 1's trigger. |
event-count-threshold | Haven't hit minCount for the trigger yet. |
session-threshold | Need more app sessions (requireMinSessionCount). |
feedback-count-threshold | Need more askFeedback responses first. |
property-mismatch | Event properties don't satisfy propertyFilters. |
cooldownDays / cooldown | Already fired recently — see Step 6. |
Step 6 — Re-test cleanly
Cooldowns persist locally, so a rule won't re-fire during a session of testing. Reset between runs:
await Boostar.reset(); // clears anonId, counters, and prompt history Step 7 — Confirm Boostar received your data
Ask your Boostar contact to confirm your test events and prompt telemetry are landing for
your app (the SDK emits boostar.review_prompt_requested whenever a prompt
fires). This closes the loop that on-device behavior reached the backend.
Error reference
| Status | Meaning |
|---|---|
401 Unauthorized | Missing or invalid x-boostar-key. |
404 Not Found | identify/events called before init for that anonId. |
400 Bad Request | Validation failure — payloads are strictly whitelisted; unknown fields are rejected. |
408 / 429 / 5xx | Transient. The SDK retries automatically; raw clients should back off and retry. |
Production checklist
Ready to ship?
Get your SDK key and your first rule configured.