Skip to main content

Payment Intents

A Payment Intent represents your intent to collect funds from a customer, giving you a durable, stateful handle on a charge across its full lifecycle. From creation through confirmation, processing, and final outcome, the intent tracks every transition and makes the terminal result available — whether synchronously in the confirm response or asynchronously via webhook.

Payment Intents are recommended for all new integrations. They provide two key advantages over the direct Payments API:

Idempotent retries. The intent ID (pmt_int_) is assigned at creation. Persist it in your database before confirming, and you can safely retry on network failures or timeouts — the same intent cannot be confirmed twice, eliminating duplicate charge risk.

Async-safe outcomes. When a processor takes longer to respond, confirm returns 202 processing instead of timing out. The final result arrives via webhook, so your integration never has to guess.

For step-by-step implementation, see the Process Card Payments guide.

Payment Intents vs Legacy

Payment IntentsLegacy (POST /payments)
FlowCreate → Confirm (→ Capture)Single request
Outcome200 succeeded or 202 processing + webhookImmediate terminal result
Retry safetyIdempotent by intent IDIDEMPOTENCY-KEY header only
Async supportBuilt-in via processing stateNot supported
Authorize & capturecapture_method: manualcapture: false
Decline handlingRe-confirm same intent with new cardCreate a new payment

Anatomy

Intent fields

FieldDescription
idUnique identifier for this intent. Prefix: pmt_int_.
account_idThe merchant account that owns the intent.
environmenttest or production.
statusCurrent lifecycle status. See Lifecycle states.
amountCharge amount in cents.
currencyThree-character ISO currency code, e.g. USD.
capture_methodautomatic (default) or manual.
payment_methodThe attached card or bank account. null if not yet provided.
payment_idID of the resulting Payment record. Populated once succeeded. Prefix: pmt_.
last_payment_errorPopulated when a confirm attempt is declined. Contains code and message.
expires_dateISO 8601 timestamp after which the intent auto-cancels.
created_dateISO 8601 timestamp.
modified_dateISO 8601 timestamp.

Sample intent

Payment Intent
{
"id": "pmt_int_2xNjK7abcdefghij",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "test",
"status": "requires_confirmation",
"amount": 1000,
"currency": "USD",
"capture_method": "automatic",
"payment_method": {
"card": {
"id": "card_AjkCFKAYiTsjghXWMzoXFPMxj",
"last_four": "4242",
"brand": "visa",
"exp_month": "12",
"exp_year": "2025"
}
},
"payment_id": null,
"last_payment_error": null,
"expires_date": "2026-04-16T14:22:10Z",
"created_date": "2026-04-15T14:22:10Z",
"modified_date": "2026-04-15T14:22:10Z"
}

Lifecycle states

StatusDescription
requires_payment_methodThe intent was created without a payment method. Call Update Payment Intent to attach a card or bank account.
requires_confirmationA payment method is attached and the intent is ready to confirm. This is also the state the intent returns to after a decline — attach a new payment method and re-confirm the same intent.
processingThe charge was submitted to the processor and the response is pending. Subscribe to the payment_intent:update webhook to receive the final outcome.
requires_captureThe card was authorized with capture_method: manual. The authorization holds funds for up to 7 days. Call Capture Payment Intent to complete the charge.
succeededThe charge was accepted. payment_id is populated with the resulting Payment record. Terminal state.
canceledThe intent was not confirmed within 24 hours, or a requires_capture authorization was not captured within 7 days. Terminal state.
An intent in requires_capture automatically moves to canceled if not captured before the expires_date. A canceled intent cannot be reactivated — create a new one.

Error responses

API errors

API errors follow the RFC 7807 Problem Details format. The most relevant codes for Payment Intent operations:

HTTP StatusWhen it occurs
400 Bad RequestInvalid request body, or an operation not valid for the intent's current state (e.g. capturing a non-authorized intent).
401 UnauthorizedMissing or invalid X-API-KEY.
403 ForbiddenAPI key lacks the required payment:write permission.
404 Not FoundIntent ID does not exist or belongs to a different account.
409 ConflictA concurrent operation is already in flight on this intent (e.g. two simultaneous confirm calls).
422 Unprocessable EntityThe request is structurally valid but cannot be processed — for example, confirming an intent that is already succeeded.

Payment declines

When the processor declines a charge, the intent returns to requires_confirmation status rather than moving to a terminal error state. The last_payment_error field is populated with the decline reason:

Declined Payment Intent
{
"id": "pmt_int_2xNjK7abcdefghij",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "test",
"status": "requires_confirmation",
"last_payment_error": {
"code": "card_declined",
"message": "Your card was declined."
},
"amount": 1000,
"currency": "USD",
"payment_id": null,
"created_date": "2026-04-15T14:22:10Z",
"modified_date": "2026-04-15T14:22:45Z"
}

You can attach a different payment method to the same intent and re-confirm — no need to create a new intent. Common last_payment_error.code values:

CodeDescription
card_declinedGeneral decline from the issuing bank.
insufficient_fundsCard has insufficient funds.
expired_cardCard expiration date has passed.
incorrect_cvcCVC verification failed.
do_not_honorIssuer declined without a specific reason.

Webhooks

Every status transition on a Payment Intent emits a payment_intent:update event to any webhooks registered on your account. The entity field contains the full intent at the time of the notification.

Webhook event: payment_intent:update
{
"id": "evnt_5jxWRFNLCAWeegrkCAG3a9DGc",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "production",
"event_type": "payment_intent:update",
"entity_type": "PaymentIntent",
"entity_id": "pmt_int_2xNjK7abcdefghij",
"entity": {
"id": "pmt_int_2xNjK7abcdefghij",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "production",
"status": "succeeded",
"amount": 1000,
"currency": "USD",
"payment_id": "pmt_5mBpT2ghiklmnopq",
"last_payment_error": null,
"succeeded_date": "2026-04-15T14:22:45Z",
"created_date": "2026-04-15T14:22:10Z",
"modified_date": "2026-04-15T14:22:45Z"
},
"created_at": "2026-04-15T14:22:45Z"
}

Check entity.status to determine the final outcome. Webhooks fire on every transition, including intermediate states like processing and requires_capture, not only terminal states.

Register a webhook for payment_intent:update if your integration may receive 202 processing responses from confirm. Without it, you will need to poll Get Payment Intent to detect the terminal state.

See Webhooks for delivery guarantees, retry behavior, and signature verification.

Where to go next