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.
| Status | Condition | Body |
|---|---|---|
200 | Token minted successfully | UnchurnTokenResponse JSON |
401 | resolveUser returned null (unauthenticated or no subscription) | empty |
405 | Request method is not POST | empty, Allow: POST header |
500 | resolveUser threw an exception | empty |
500 | resolveUser returned a non-object or invalid shape | empty |
500 | Token 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:
subscriptionIdis not a non-empty string → 500modeis not'test','live', orundefined→ 500subscriptionIdcontains characters outside[A-Za-z0-9_-]→ 500 (detected at mint time byassertValidIdentifier)
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(),
}),
});| Field | Type | Description |
|---|---|---|
error.code | ApiErrorCode | Machine-readable code (see table below) |
error.message | string | Human-readable description |
error.docs | string (URL) | Optional documentation link |
error.example_file | string | Optional 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
| Code | HTTP status | When it fires | What to do |
|---|---|---|---|
bad_request | 400 | Malformed request body | Fix the widget integration |
unauthorized | 401 | All 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 |
forbidden | 403 | General policy rejection | — |
direct_cancel_disabled | 403 | Cancel Now clicked but direct_cancel_access was false at session creation | — |
not_found | 404 | Session or resource not found | — |
conflict | 409 | General conflict | — |
stripe_not_connected | 409 | Stripe Connect not yet completed for this merchant | Complete the Stripe Connect OAuth flow |
session_already_completed | 409 | /complete called twice for the same terminal outcome; Stripe side-effect already committed | Widget dismisses quietly — idempotent |
refresh_required | 409 | Stale flow-config or session snapshot | Widget should refresh the session and retry |
pause_not_eligible_interval | 409 | Pause offer unavailable (non-monthly billing cadence) | — |
unsupported_plan_type | 422 | Subscription price shape is outside the support matrix (metered, tiered, one-time) | See supported subscriptions |
rate_limited | 429 | Too many requests | Back off and retry |
stripe_error | 502 | Stripe API returned an error | Check Stripe status; retry |
manual_cancellation_unavailable | 503 | Transient infra or Stripe failure on manual-cancellation request | Retry |
operation_in_progress | 503 | Concurrent mutation already claimed the session | Widget retries shortly |
internal | 500 | Unexpected server error | Contact 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.
Cross-links
- Widget API —
createUnchurnHandler— handler that emits 401/405/500 - Widget API —
useUnchurn— hookerrorfield - Supported subscriptions — eligibility constraints that surface as 409 / 422
Last updated on