Skip to main content

Error Handling and Operational Notes

Hilt is intentionally not hiding every backend failure behind a single generic envelope. That means your client should be prepared to handle standard FastAPI error responses and a few route-specific behaviors.

Error shape

Most route failures come back as:

{
"detail": "Human readable message"
}

Examples:

{
"detail": "Authentication required"
}
{
"detail": "Payment session has expired"
}

Some older surfaces may still emit lightweight error or message fields. If you are building a backend client, normalize both patterns:

  • prefer detail first
  • fall back to message
  • then fall back to error

Common status codes

400 Bad Request

Used when the route itself is well-formed but the action is invalid.

Examples:

  • unsupported product_type
  • malformed OAuth state
  • invalid Solana RPC payload

401 Unauthorized

Used when the caller is missing valid auth or presents an invalid secret.

Examples:

  • bad JWT
  • revoked API key
  • invalid Telegram webhook secret

403 Forbidden

Used when the caller is authenticated but not allowed.

Examples:

  • suspended account
  • non-staff caller attempting an admin-only action
  • connector handoff secret mismatch

404 Not Found

Used when the resource does not exist or is not visible to the caller.

Examples:

  • product id not found
  • receipt id not found
  • ticket id not found

409 Conflict

Used when the current state makes the request invalid.

Examples:

  • payment session already linked to a different transaction
  • ticket closed
  • email already registered

410 Gone

Used for expired buyer-side sessions.

Examples:

  • checkout payment session expired
  • identity session expired

422 Unprocessable Entity

Used for validation errors and state checks.

Examples:

  • invalid Solana wallet address
  • identity verification required before confirmation
  • unsupported key permissions
  • bad billing payload

429 Too Many Requests

Used for:

  • API key plan limits
  • rate limiting on /v1/*

Rate-limited responses may include:

  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset
  • Retry-After

Practical rule:

  • treat the response headers as authoritative
  • do not hard-code your own idea of when a limit resets
  • back off and retry after Retry-After when it is present

503 Service Unavailable

Used when a required runtime dependency is not configured or is temporarily unavailable.

Examples:

  • Stripe billing not configured
  • Hilt fee wallet missing
  • SOL pricing oracle unavailable or stale
  • connector handoff not configured

Buyer checkout state machine

The hosted checkout is easiest to integrate if you treat payment state as a lifecycle, not a single button press.

Typical progression:

  1. PENDING_SIGNATURE
  2. PENDING_CONFIRMATION
  3. CONFIRMED

Possible failure states:

  • FAILED
  • expired session
  • delivery failure while payment is still confirmed

Important rule:

  • do not ask the buyer to sign again just because confirmation is still pending
  • keep the original payment_id
  • keep the original tx_signature
  • use GET /v1/payments/{payment_id} to track state

This is especially important for mobile wallet flows where app-switch timing can make the UI feel behind the chain for a short period.

Identity and delivery failure handling

For Telegram or Discord membership flows:

  • confirm the identity handshake before payment confirmation
  • if delivery fails after payment settles, the membership may still be valid
  • use:
POST /v1/memberships/{membership_id}/retry-delivery

That lets you recover delivery without replaying the payment itself.

API key operational guidance

Treat keys as server-side credentials:

  • generate them in the dashboard or merchant app
  • store them in a secrets manager
  • never expose them in browser code
  • rotate on personnel or deployment changes
  • inspect usage_summary and recent_activity to understand what each key is doing

Current plan-level key quotas are documented in Security Best Practices.

Practical retry guidance

Safe retry candidates:

  • GET requests
  • GET /v1/payments/{payment_id}
  • POST /v1/memberships/{membership_id}/retry-delivery
  • POST /v1/billing/refresh/stripe

Be more careful with:

  • POST /v1/products
  • POST /v1/keys
  • POST /v1/support/tickets
  • POST /v1/pay/confirm

For those, keep your own idempotency or local request tracking if you are building a custom backend workflow around Hilt.

Practical async guidance

The most common developer mistake is treating settlement, membership activation, delivery, and receipt visibility as one atomic browser event.

They are related, but they are not the same moment.

Safer pattern:

  1. keep payment_id
  2. poll GET /v1/payments/{payment_id}
  3. once confirmed, read membership state
  4. if needed, read receipt state
  5. if delivery alone failed, retry delivery instead of replaying payment

For the provider side of that flow, read Webhooks.