Docs menu

Core Concepts

Errors

Every error response uses the same envelope. Switch on error.code, not error.message — message wording isn't guaranteed stable across releases.

{
  "error": {
    "type": "invalid_request",
    "code": "unsafe_webhook_url",
    "message": "Webhook URL resolves to a private or reserved address.",
    "request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }
}

request_id is a bare UUIDv4, matching the X-Request-Id response header on every response, success or error.

HTTP status → error type

HTTPtype
400invalid_request
401authentication_failed
403permission_denied
404not_found
409conflict
422validation_error
429rate_limited
500internal_error
503service_unavailable

Codes worth handling explicitly

400 invalid_request

  • unsafe_webhook_urlWebhook URL resolves to a private/internal address.
  • invalid_cursorMalformed pagination cursor.

401 authentication_failed

  • timestamp_expiredTimestamp outside the 5-minute window.
  • signature_invalidSignature doesn't match the raw request body.
  • replay_detectedNonce was already used within the window.

403 permission_denied

  • key_inactiveThe API key is known but not active.

409 conflict

  • idempotency_key_conflictSame Idempotency-Key reused with a different request body.
  • request_in_progressSame key + body, previous attempt hasn't reached a definitive outcome yet.

503 service_unavailable

  • bank_timeoutThe bank connector didn't respond in time. The payment's true outcome is unknown and it is not marked failed — retry the same Idempotency-Key later.

Other 5xx responses: retry with exponential backoff — this is a Prosoft Pay-side incident, not a request problem. 429 rate_limited does not currently return a Retry-After header, so use your own backoff (a 60-second sliding window per API key).