Skip to Content
ReferenceError codes

Error codes

HTTP error codes returned by the token signing handler and the widget session API.


Token signing handler errors

createUnchurnHandler returns these HTTP responses. Source: packages/widget/src/nextjs.ts.

StatusConditionBody
200Token minted successfullyUnchurnTokenResponse JSON
401resolveUser returned null (unauthenticated or no subscription)empty
405Request method is not POSTempty, Allow: POST header
500resolveUser threw an exceptionempty
500resolveUser returned a non-object or invalid shapeempty
500Token mint failed (e.g. subscriptionId contains illegal characters)empty

All error bodies are empty — no information is leaked to the browser. Errors are logged server-side.

resolveUser shape validation:

If resolveUser returns a value where:

  • subscriptionId is not a non-empty string → 500
  • mode is not 'test', 'live', or undefined → 500
  • subscriptionId contains characters outside [A-Za-z0-9_-] → 500 (detected at mint time by assertValidIdentifier)

Widget session API errors

The Unchurn backend returns structured errors for widget requests. Source: packages/contracts/src/errors.ts.

Error response shape

// packages/contracts/src/errors.ts export const apiErrorResponseSchema = z.object({ error: z.object({ code: apiErrorCodeSchema, message: z.string(), docs: z.string().url().optional(), example_file: z.string().optional(), offer_kind: offerKindSchema.optional(), }), });
FieldTypeDescription
error.codeApiErrorCodeMachine-readable code (see table below)
error.messagestringHuman-readable description
error.docsstring (URL)Optional documentation link
error.example_filestringOptional suggested file path for the fix
error.offer_kind'plan_switch' | 'trial_extension' | 'pause' | 'discount'Which retention offer the rejection refers to (only on policy-rejection errors)

Error codes

CodeHTTP statusWhen it firesWhat to do
bad_request400Malformed request bodyFix the widget integration
unauthorized401All token-related auth failures (missing, invalid, expired, mode mismatch, signing not configured) collapse to this single code. Check the response message for detail.Refresh the token (useUnchurn handles this automatically); confirm UNCHURN_SECRET matches the dashboard
forbidden403General policy rejection
direct_cancel_disabled403Cancel Now clicked but direct_cancel_access was false at session creation
not_found404Session or resource not found
conflict409General conflict
stripe_not_connected409Stripe Connect not yet completed for this merchantComplete the Stripe Connect OAuth flow
session_already_completed409/complete called twice for the same terminal outcome; Stripe side-effect already committedWidget dismisses quietly — idempotent
refresh_required409Stale flow-config or session snapshotWidget should refresh the session and retry
pause_not_eligible_interval409Pause offer unavailable (non-monthly billing cadence)
unsupported_plan_type422Subscription price shape is outside the support matrix (metered, tiered, one-time)See supported subscriptions
rate_limited429Too many requestsBack off and retry
stripe_error502Stripe API returned an errorCheck Stripe status; retry
manual_cancellation_unavailable503Transient infra or Stripe failure on manual-cancellation requestRetry
operation_in_progress503Concurrent mutation already claimed the sessionWidget retries shortly
internal500Unexpected server errorContact support

useUnchurn error state

The hook’s error field is an Error | null. It reflects:

  • Token endpoint returned a non-2xx status: Error('Token endpoint returned HTTP <status>')
  • Token endpoint returned a malformed response body: a Zod validation error describing which field was wrong

These surface as the hook’s error field (not thrown), so the merchant can show a degraded UI instead of crashing.


Last updated on