SetupIntent
with payment_method_data
and confirming it in one request. Use this when you can (and are willing to) handle raw card data server-side.PCI-DSS scope notice
Posting PAN/CVC directly to your servers places you in SAQ D scope. If you want to minimize PCI burden, use Drop-in or Checkout instead.
Item | Notes |
---|---|
Secret key | Use your server key only on the backend. |
Customer ID | Reuse or create with POST /v1/customers (email recommended). |
Redirect endpoint | A front-end page to receive the user after 3-D Secure (return_url ). |
Webhooks | Expose an HTTPS endpoint. Treat setup_intent.succeeded as the only authoritative success signal. |
{
"confirm": true,
"customer": "cus_1838767497487056896",
"payment_method_types": ["card"],
"payment_method_data": {
"type": "card",
"card": {
"exp_month": "04",
"exp_year": "2027",
"number": "4761344136141390",
"cvc": "022",
"name": "XX XXX"
}
},
"usage": "on_session",
"return_url": "https://yourwebsite.com"
}
Field | Required | Description |
---|---|---|
confirm | ✔ | Set true to attempt confirmation immediately. |
customer | ✔ | The customer to attach the card to. |
payment_method_data | ✔ | Raw card details (PCI scope). |
usage | ✔ | on_session (user present now). Future charges can still be off_session . |
return_url | ✔ when 3DS possible | Where to send the cardholder back after challenge. |
status: "succeeded"
→ you’ll still confirm via webhook (see §4).status: "requires_action"
with next_action.type = "challenge_redirect"
.status: "requires_payment_method"
plus error details.{
"id": "seti_1838873795297804288",
"object": "setup_intent",
"status": "requires_action",
"customer": "cus_1838767497487056896",
"client_secret": "seti_..._secret_...",
"next_action": {
"type": "challenge_redirect",
"challenge_redirect": {
"url": "https://XXXXXXXXXXX",
"return_url": "https://yourwebsite.com"
}
},
"payment_method": "pm_1838873795201335296",
"payment_method_options": {
"card": {
"request_three_d_secure": "auto",
"setup_future_usage": "off_session"
}
}
}
requires_action
next_action.type = "challenge_redirect"
:next_action.challenge_redirect.url
.return_url
.Optional: You may poll GET /v1/setup_intents/{id}
on your server, but the source of truth is the webhook event.
Event | Purpose | Typical action |
---|---|---|
setup_intent.created | Session starts | Optional logging / analytics |
setup_intent.succeeded | Saved successfully. | Persist (customer_id, payment_method_id) mapping |
setup_intent.succeeded
****- Payload demo{
"id": "evt_1953045921369423872",
"object": "event",
"created": 1754477408000,
"livemode": false,
"data": {
"object": {
"id": "seti_1953034584329289728",
"object": "setup_intent",
"created": 1754474705000,
"livemode": false,
"status": "succeeded",
"metadata": {
"a": "1",
"b": "2"
},
"customer": "cus_1952983283688013824",
"client_secret": "seti_1953034584329289728_secret_QNWoBNDOZpW4bomjsAgC8DF1",
"payment_method_types": [
"card"
],
"payment_method_options": {
"card": {
"request_three_d_secure": "auto",
"setup_future_usage": "off_session"
}
},
"return_url": "https://checkouttest.wooshpay.com/setup/cs_test_1953034583884693504?key=cGtfdGVzdF9OVEUyTWpZeE1ETTNOekExT1RVek16SXdPVFl4T25SRWFEUjRhbWxhVUcxM1RFeG1aMXBGUVdwd2RGVlRaakUyTnpZMU1qZ3pNamswT1RN",
"payment_method": "pm_1953045864444329984"
}
},
"type": "setup_intent.succeeded"
}
{
"amount": 2500,
"currency": "USD",
"confirm": true,
"off_session": false, // true if no user present
"customer": "cus_1953770230215868416",
"payment_method": "pm_123123123",
"return_url": "https://example.com/pay/complete"
}
Key point | Value |
---|---|
customer | ID from Step 1. |
payment_method | Saved card’s ID from webhook or listing API. |
off_session | false to attempt and on-session charge(user present now)true to attempt an off-session charge (recurring / unsupervised). |
Action | Test card number | Notes |
---|---|---|
Successful save + charge | 4111 4111 41111 4111 | Any future date, any CVC |
3-D Secure required | 4462030000000000 | Checkout will prompt for 3DS challenge |
Task | API |
---|---|
Create session | POST /v1/checkout/sessions/setup |
Listen to events | setup_intent.created``setup_intent.succeeded |
List cards | GET /v1/customers/{id}/payment_methods |
Charge card | POST /v1/payment_intents (with customer + saved payment_method) |