REST API
The four public endpoints the SDK + ad networks talk to. All hosted on Cloudflare Workers — fast, cheap, edge-routed.
Authentication
Every authenticated endpoint expects three headers:
| Header | Value |
|---|---|
X-Reflect-Company-Key | co_live_… — your tenant identifier |
X-Reflect-App-Key | app_live_… — per-app identifier |
X-Reflect-Signature | Lowercase hex HMAC-SHA256 of the body bytes, using your app’s SigningSecret |
If Content-Encoding: gzip is sent, the signature is computed over the compressed wire bytes. Server verifies, then decompresses.
POST /event
Submit a batch of events.
POST /event HTTP/1.1
Host: reflect.bablu147147.workers.dev
Content-Type: application/json
Content-Encoding: gzip ← optional, ≥10 events recommended
X-Reflect-Sdk: 2.1.0
X-Reflect-Company-Key: co_live_…
X-Reflect-App-Key: app_live_…
X-Reflect-Signature: <64-char hex>
{
"events": [ { "event_id": "...", "event_name": "...", "install_uuid": "...", ... } ],
"sent_at_ms": 1735999999000
}Response (always 200 once auth passes; per-event errors in body):
{
"accepted": 47,
"rejected": 3,
"results": [
{ "event_id": "abc", "status": "accepted" },
{ "event_id": "def", "status": "rejected", "reason": "bad_event_id" }
],
"audit_key": "audit/2026/04/26/co1/app42/abc-receivedat.jsonl.gz"
}Error responses:
| Status | Reason |
|---|---|
| 400 | missing_auth_headers, empty_body, bad_json, bad_batch_shape, bad_gzip |
| 401 | unknown_app_key, unknown_company_key, company_suspended, app_company_mismatch, bad_signature |
| 413 | body_too_large, decompressed_too_large |
| 429 | cap_exceeded (Free tier exhausted) |
| 503 | audit_write_failed |
GET /l/:link_id
Tracking link redirect. Public, no auth. Pipeline:
- Edge filter (UA / ASN / country)
- Honeypot check (auto-block source IP if hit)
- Rate limiter (per IP /24 sliding window)
- Click row written to D1
- 302 to the platform-specific store URL with attribution params encoded
Query parameters honored:
| Param | Effect |
|---|---|
click_id | Partner-supplied ID stored as ext_click_id |
sub1–sub5 | Free-form fields, threaded to attribution + postbacks |
mobile_only=1 | Drop desktop UA |
POST /privacy/delete
GDPR / CCPA right-to-be-forgotten. Same auth headers as /event. Body:
{ "install_uuid": "8f2a1c0e94d7423b8b53af7c9e21d630" }Returns 202 { "ok": true, "queued": true }. Server queues the request; nightly cron drains in batches of ≤25 requests × ≤1000 rows/table to keep D1 row writes bounded.
POST /skan-postback
Apple SKAdNetwork postbacks. Public, no auth (Apple signs the payload). Body is Apple’s standard JSON. Reflect persists to R2 audit and returns 200.
To register Reflect as your SKAN postback endpoint, add to your iOS app’s Info.plist:
<key>NSAdvertisingAttributionReportEndpoint</key>
<string>https://reflect.bablu147147.workers.dev/skan-postback</string>GET /health
Liveness probe. Returns 200 with binding status (D1, R2, KV, Queues). Used by the login footer’s status link.
Rate limits
None enforced server-side beyond per-tenant caps (see Plans & billing). Cloudflare’s native DDoS protection applies; if you’re hitting it accidentally, contact us.