Webhooks and errors
Handle errors
A failed Payments API call throws PaymentsError in TypeScript and a subclass of TimbroError in Python. Invoices uses InvoiceError, which also exposes failure and, when retained, invoiceId. A declined attempt is not an HTTP error: the payment stays in requires_customer_action with lastDecline, and the customer can try again.
| Response | What to do |
|---|---|
400 |
Fix the fields listed in problems. |
422 in Invoices |
Review problems and issuanceBlockers. Without an invoiceId, no invoice was created; correct the request and call create again. |
401 or 403 |
Check the key, business, mode, and permissions. |
404 |
Check the identifier and the key used for retrieval. |
409 |
Check code: the reference may have different data, or the action may no longer be valid. |
429 or 503 |
Respect Retry-After if present, and keep the same request. |
Timeout or 5xx |
Recover the outcome before trying another payment. |
import { PaymentsError } from "@timbro/payments";
export function supportDetails(error: unknown) { if (!(error instanceof PaymentsError)) throw error; return { code: error.code, requestId: error.requestId, problems: error.problems };}APIError and its subclasses, such as InvalidRequestError or ConflictError, mean that Timbro answered with an error. APIConnectionError means that no response arrived, and its code says what that means: connection_failed when the request never left or a read broke off, request_timed_out when a read timed out, and outcome_unknown when a write may have reached Timbro. In the first two cases nothing changed. The TypeScript SDK reports all three as transport_failure.
from timbro_payments import APIError, TimbroError
def support_details(error: TimbroError) -> dict[str, object]: details: dict[str, object] = {"code": error.code, "request_id": error.request_id} if isinstance(error, APIError): details["problems"] = error.problems return detailsFor Invoices, also retain the invoice ID and blockers requiring intervention:
import { InvoiceError } from "@timbro/invoices";
export function invoiceSupportDetails(error: unknown) { if (!(error instanceof InvoiceError)) throw error; return { code: error.code, requestId: error.requestId, invoiceId: error.invoiceId, failure: error.failure, problems: error.problems, issuanceBlockers: error.issuanceBlockers, };}An invoice that was already created can end up rejected or operatorRequired after a successful request. Retrieve that same invoice; follow correct an e-CF to replace a rejected one. After a lost response, search by invoiceReference or resend the same request with that reference before issuing another document.
Save these details with the payment identifier for support. Do not log keys, card data, or full Checkout links.
isRetryable describes the error; it does not authorize retrying every operation. Payment and refund creation retain their identity for retries. Check the conditions for other operations in the REST reference.
In Python, retryable means the same. When outcome_unknown is True, the operation may have taken effect even though the call failed. Recover the outcome before repeating it; the error text says how to find the result.