Troubleshooting
The failures integrators actually hit.
Every request returns unauthorized
Check the header is Authorization: Bearer tk_live_.... We answer identically for an unknown key, a revoked one and an expired one: telling you which would confirm to anyone holding a leaked string that it was once real.
forbidden_scope on an endpoint you expected to work
The response names the scope in details.scope. Note that write does not imply read.
Claims fail with already_claimed
That shopper already has this trial. It is a state, not an error in your request, which is why it is a 409.
Claims fail with blocked_by_risk
Risk settings stopped it. We deliberately do not say which signal fired: that would be a tuning guide for whoever triggered it. Held claims appear in your dashboard for review.
Events seem to vanish
They are queued, not written synchronously, and /events returns 202. If you send the same idempotency-key twice, the second is deliberately a no-op.
Webhook signature never matches
Almost always the body. Sign the raw bytes you received, not a re-serialised object. Many frameworks parse JSON before your handler runs; ask yours for the raw body.
Webhooks stopped arriving
Check your subscription's status. After enough consecutive failures we disable it and record why. Fix the endpoint and re-enable it: we do not re-enable on our own, because a subscription that came back without you knowing is worse than one that stayed off.
The widget renders nothing
By design when there is no eligible offer, or when anything failed. Call decide() through the SDK with an onNoOffer handler to see the reason the engine gave.
rate_limited
Limits are per key. Back off for the retry-after seconds. If one integration is noisy, give it its own key rather than raising the limit on all of them: budgets are per key precisely so one runaway cannot starve the others.