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:
- An attempt in flight. An intent is never sealed under an active attempt.
- 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.
expired if it holds no attempts, failed otherwise. The corresponding webhook — payment.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.