Skip to main content
A payment intent has no deadline of its own. Its status always derives from two things: what its attempts did, and how long it has been waiting. This page maps every state, every transition, and every clock so you can build your integration on the exact behaviour.

The two resources

  • The payment intent is what you create and what your customer owes. It is the source of truth for the outcome.
  • An attempt is one try at collecting the intent through one payment method. An intent can hold several attempts (up to 4), but only one can be in flight at a time.

Intent states

expired means exactly one thing: the payment was never attempted. A payment your customer engaged with never expires — it fails, so your reconciliation can always tell abandonment before checkout from failure during it.

Attempt states

Each waiting state carries its own entry timestamp (processing_at, action_required_at) so you can see exactly how long an attempt has been waiting, and each terminal state carries its own (completed_at, failed_at, expired_at).

How attempts drive the intent

The intent reacts the moment an attempt finalises — there is no polling delay on these transitions:

Time windows

Two things protect a pending intent from its inactivity window:
  1. An attempt in flight. An intent is never sealed under an active attempt.
  2. An open hosted checkout session. The session carries the expiry you chose at creation (expires_in_minutes); as long as it is open, the intent stays payable even past its own window.
When the window runs out with no protection, the intent is sealed: expired if it holds no attempts, failed otherwise. The corresponding webhookpayment.expired or payment.failed — fires at that moment.

Recovering a sealed payment

failed and expired are terminal for the payer but not for you: a requery re-verifies the active attempt with its provider and reopens the payment when the provider actually settled it after the window closed.