Troubleshooting

Symptom first, then the cause we see most, then the fix. Every error body the API returns carries a request_id — include it when you write to us. Errors from the gateway in front of the API carry none; see API errors.

Symptom Most likely cause Fix
401 on every call Key from the other environment, revoked, or malformed header Check the prefix (sk_test_ vs sk_live_) against the base URL; send Authorization: Bearer sk_…. unauthorized
400 with Idempotency-Key header is required on POST POST without the header Send a stable key from your system on every POST. bad-request
409 idempotency-key-reused The key belongs to another body on this operation: a request that succeeded, is still running, or ended in a 5xx or a timeout Repeat the original body to get the original result, or use a new key for a new request, including a corrected body after a 5xx or a timeout. idempotency-key-reused
Same POST created two resources Different Idempotency-Key on the retry Reuse the key on retries; for churches, send your reference so identity does not depend on the key
404 for a resource you just created The key belongs to another partner, or the id was typed 404 is "not found or not yours". Check the key's partner. not-found
400 church_id is not registered for this partner Your platform never registered the church, or it is in the other environment Register or import the church first and use the id from the response. bad-request
This link can't be opened, or a blocked frame with a frame-ancestors console error, when your page opens the flow The page's site is not in your allowed origins. Scheme, host and port must match exactly: http://localhost:3000 is not http://localhost:5173 Add the site with PUT /v1/allowed_origins; it reaches the flow within a few minutes. The link is not spent: if GET /v1/donor_sessions/{id} still says created, open the same url again. Open the flow on your page
410 opening the hosted flow The session expired (30 minutes) or was already used, or the page's site is not in your allowed origins Check GET /v1/allowed_origins first; otherwise create a new session; never reuse a URL. gone
429 with a problem body More than 600 requests in a minute Wait Retry-After seconds, then retry. rate-limited
429, 500 or 503 whose body is only a message The gateway in front of the API: its throttle, shared by every partner, or a timeout Retry with exponential backoff and the same Idempotency-Key; log the UTC time and the operation. API errors
400 with an HTML body, or 403 with an empty body to a browser preflight The gateway refused the request before the API saw it: a path it cannot parse (such as %zz), or a call made from a web page Percent-encode ids when you build the path; call the API from your backend. Neither response carries X-Request-Id. API errors
503 with a problem body (unavailable) The operation is not enabled in this environment Do not retry in a loop; check the environment. unavailable
Other 5xx with a problem body Our fault Retry with the same Idempotency-Key. internal
The feed keeps returning the same events The cursor from the last page is not being sent back, or a null replaced it Send next_cursor as since_cursor on the next poll and store it after each page that has events. An empty page can return next_cursor: null: keep the cursor you already stored.
No webhook arrived Endpoint unreachable, or signature rejected on your side and the retry budget ran out Run POST /v1/webhook_endpoints/{id}/test — it reports the status your endpoint returned. Reconcile activity from GET /v1/events and donation.updated / donor.updated from GET /v1/financial_events, each with its own cursor
Donor sees "Paycheck giving isn't enabled for this church yet" Roster hasn't verified the church's legal setup and receiving account Complete both; on a new church, payout_ready=true on GET /v1/churches/{id} confirms it. legal setup, status after verification, when a church is ready
Signature never verifies Verifying the parsed JSON instead of the raw bytes, a secret with its whsec_ prefix removed or decoded, the header read with the wrong case (Node lowercases header names: use req.headers["roster-signature"]), or clock skew over five minutes HMAC the exact bytes received as {t}.{raw body}, keyed with the whole whsec_ string; fix the clock. Copy the tested verifier and check it against the test vector. webhooks
The flow finished on your page but no enrollment.activated Payroll not connected yet, or its confirmation has not reached Roster Trust the feed, not the end of the flow. On sandbox, connect the payroll in the test window inside the flow, or call POST /sandbox/simulate/payroll-connected
Certification item stays off The scenario did not reach the feed The screen reads the feed, not a checkbox — run the scenario end to end. certification

Still stuck? Write to us with the call's X-Request-Id (req_…; on an error it is also request_id in the body) and the Idempotency-Key you sent — those two identify the exact call on our side.