Accept Card Payments
This guide creates a payment with a card from your server. The card is either already saved, or it arrives in the payment request.
- A saved card needs a card you saved first with Save Cards. Create the payment with Payment Intents or the Payments API.
- Card details in the request come from your servers in the payment request. Your organization must be PCI certified.
Before you start
Secret Key- Authenticates requests from your server. Never send it to the browser.
Pay with a Saved Card
Creating a Payment
Use the id of a saved card and create the payment from your server using your Secret Key.
You can create card payments using either Payment Intents or the direct Payments API. Both approaches result in a Payment object. We recommend Payment Intents for new integrations. They keep state across the whole lifecycle, report slow outcomes by webhook and retry safely.
- Payment Intents
- Legacy
A Payment Intent represents your intent to collect funds from a customer. It tracks the full lifecycle of a charge — creation, confirmation, processing, and the final succeeded state — and creates a Payment record on success.
Create a Payment Intent
Call the Create Payment Intent endpoint. Including a card at creation time starts the intent in requires_confirmation status and it is ready to confirm immediately:
curl 'https://api.publicsquare.com/payment_intents' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'IDEMPOTENCY-KEY: 09ec2c87-7fb8-44ca-bb18-5c71a76974da' \
-H 'Content-Type: application/json' \
-d '{
"amount": 1000,
"currency": "USD",
"capture_method": "automatic",
"payment_method": {
"card": "card_AjkCFKAYiTsjghXWMzoXFPMxj"
},
"customer": {
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com"
},
"billing_details": {
"address_line_1": "111 Test St.",
"city": "Des Moines",
"state": "IA",
"postal_code": "51111",
"country": "US"
}
}'
{
"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",
"bin": "424242",
"brand": "visa",
"exp_month": "12",
"exp_year": "2025"
}
},
"payment_id": null,
"expires_date": "2026-04-16T14:22:10Z",
"created_date": "2026-04-15T14:22:10Z",
"modified_date": "2026-04-15T14:22:10Z"
}
Create Without a Payment Method
For multi-step checkout flows where the payment method is collected separately, omit the payment_method field. The intent starts in requires_payment_method status and the amount and other details can still be updated before a card is attached:
curl 'https://api.publicsquare.com/payment_intents' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'IDEMPOTENCY-KEY: 09ec2c87-7fb8-44ca-bb18-5c71a76974da' \
-H 'Content-Type: application/json' \
-d '{
"amount": 1000,
"currency": "USD",
"capture_method": "automatic"
}'
{
"id": "pmt_int_2xNjK7abcdefghij",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "test",
"status": "requires_payment_method",
"amount": 1000,
"currency": "USD",
"capture_method": "automatic",
"payment_method": null,
"payment_id": null,
"expires_date": "2026-04-16T14:22:10Z",
"created_date": "2026-04-15T14:22:10Z",
"modified_date": "2026-04-15T14:22:10Z"
}
Once you have a card, call the Update Payment Intent endpoint to attach it. The intent will transition to requires_confirmation:
curl 'https://api.publicsquare.com/payment_intents/pmt_int_2xNjK7abcdefghij' \
-X 'PUT' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'Content-Type: application/json' \
-d '{
"payment_method": {
"card": "card_AjkCFKAYiTsjghXWMzoXFPMxj"
}
}'
Confirm the Payment Intent
With the intent in requires_confirmation, call the Confirm Payment Intent endpoint to submit the charge to the processor:
curl 'https://api.publicsquare.com/payment_intents/pmt_int_2xNjK7abcdefghij/confirm' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'IDEMPOTENCY-KEY: 3a9f1b72-cc4e-4f92-b7d3-1e5a9c8d2041' \
-H 'Content-Type: application/json'
When the processor responds quickly, you will receive a 200 with status: succeeded and a populated payment_id:
{
"id": "pmt_int_2xNjK7abcdefghij",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "test",
"status": "succeeded",
"amount": 1000,
"currency": "USD",
"payment_id": "pmt_5mBpT2ghiklmnopq",
"next_action": null,
"expires_date": null,
"succeeded_date": "2026-04-15T14:22:45Z",
"created_date": "2026-04-15T14:22:10Z",
"modified_date": "2026-04-15T14:22:45Z"
}
When the processor takes longer to respond, you will receive a 202 with status: processing:
{
"id": "pmt_int_2xNjK7abcdefghij",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "test",
"status": "processing",
"amount": 1000,
"currency": "USD",
"payment_id": null,
"expires_date": "2026-04-15T14:52:10Z",
"created_date": "2026-04-15T14:22:10Z",
"modified_date": "2026-04-15T14:22:35Z"
}
202, subscribe to the payment_intent:update webhook event. The final outcome is in entity.status on the Payment Intent in the notification payload. See the Webhooks guide for setup instructions.{
"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",
"status": "succeeded",
"amount": 1000,
"currency": "USD",
"payment_id": "pmt_5mBpT2ghiklmnopq",
"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"
}
Handling Declines
If the card is hard-declined, the intent returns to requires_confirmation and the last_payment_error field is populated with the decline reason. You can attach a new payment method and re-confirm the same intent — no need to create a new one:
{
"id": "pmt_int_2xNjK7abcdefghij",
"status": "requires_confirmation",
"last_payment_error": {
"code": "card_declined",
"message": "Your card was declined."
},
...
}
The direct Payments API provides a synchronous, single-request charge. The response contains the final payment result.
Create a Payment
Use the id of the card you collected above. Make a call to the Create Payment endpoint:
curl 'https://api.publicsquare.com/payments' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'IDEMPOTENCY-KEY: 09ec2c87-7fb8-44ca-bb18-5c71a76974da' \
-H 'Content-Type: application/json' \
-d '{
"amount": 1000,
"currency": "USD",
"payment_method": {
"card": "card_AjkCFKAYiTsjghXWMzoXFPMxj"
},
"customer": {
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com"
},
"billing_details": {
"address_line_1": "111 Test St.",
"city": "Des Moines",
"state": "IA",
"postal_code": "51111",
"country": "US"
}
}'
The amount is in cents. 1000 is $10.00.
Payment Response
Given the above example, you should see a payment result similar to:
{
"id": "pmt_2YKewBonG4tgk12MheY3PiHDy",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "test",
"status": "succeeded",
"transaction_id": "trx_95rvMJvAVeG68W4NtLdfkN3LG",
"amount": 1000,
"amount_capturable": 0,
"amount_charged": 1000,
"amount_refunded": 0,
"refunded": false,
"currency": "USD",
"payment_method": {
"card": {
"id": "card_AjkCFKAYiTsjghXWMzoXFPMxj",
"cardholder_name": "John Smith",
"last4": "4242",
"bin": "424242",
"exp_month": "12",
"exp_year": "2025",
"brand": "visa",
"avs_code": "Y",
"cvv2_reply": "M",
"fingerprint": "CC2XvyoohnqecEq4r3FtXv6MdCx4TbaW1UUTdCCN5MNL"
}
},
"customer": {
"id": "cus_7Ay5mcUXAxwrN6wQEQUVEHBCJ",
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com"
},
"billing_details": {
"address_line_1": "111 Test St.",
"city": "Des Moines",
"state": "IA",
"postal_code": "51111",
"country": "US"
},
"transaction": {
"id": "trx_95rvMJvAVeG68W4NtLdfkN3LG",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "test",
"status": "succeeded",
"amount": 1000,
"fee_amount": 0,
"net_amount": 1000,
"currency": "USD",
"type_id": "pmt_2YKewBonG4tgk12MheY3PiHDy",
"type": "payment",
"processor": "nuvei",
"processor_id": "7110000000010554769",
"created_at": "2024-06-30T01:02:29.212Z",
"modified_at": "2024-06-30T01:02:29.212Z"
},
"created_at": "2024-06-30T01:02:29.212Z",
"modified_at": "2024-06-30T01:02:29.212Z"
}
Pay with Card Details
Send the card details in the same request that creates the payment. You do not need to save the card first. The API saves the card to the customer and returns its id for later payments.
card_details, so pay with a saved card to create an intent with the card id. Use card_details with Create Payment when you want to send the card and charge it in one request.A restricted key needs the payment-method:write or payment-method:capture permission, in addition to the permission to create payments.
To send a payout with card details, see Send Card Payouts.
Send Card Details
Send the request to the secure hostname for the mode of your API key. The secure hostname turns the card into a token before the request reaches the Credova API.
| Mode | Hostname | API key |
|---|---|---|
| Live | https://secure.payments.credova.com | Live Secret Key |
| Test | https://secure.payments.sbx.credova.com | Test mode Secret Key |
Put the card in payment_method.card_details:
| Field | Required | Description |
|---|---|---|
cardholder_name | Yes | Name on the card. |
number | Yes | Card number, digits only. |
expiration_month | Yes | Expiration month, from 1 to 12. |
expiration_year | Yes | Four-digit expiration year. |
cvc | No | Card security code. A card sent without a CVC can decline more often. |
The request also needs customer (or customer.id) and billing_details. The rest of the body is the same as a request to https://api.publicsquare.com.
Process a Payment
A payment with card_details also needs device_information.ip_address, the IP address of the customer's device.
Create a Payment with Card Details
curl 'https://secure.payments.credova.com/payments' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'IDEMPOTENCY-KEY: 09ec2c87-7fb8-44ca-bb18-5c71a76974da' \
-H 'Content-Type: application/json' \
-d '{
"amount": 1000,
"currency": "USD",
"payment_method": {
"card_details": {
"cardholder_name": "John Smith",
"number": "4242424242424242",
"expiration_month": 12,
"expiration_year": 2030,
"cvc": "123"
}
},
"customer": {
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com"
},
"billing_details": {
"address_line_1": "111 Test St.",
"city": "Des Moines",
"state": "IA",
"postal_code": "51111",
"country": "US"
},
"device_information": {
"ip_address": "203.0.113.10"
}
}'
To test, send the same request to https://secure.payments.sbx.credova.com with your test mode Secret Key and a test card.
Create with Existing Customer
If you created a customer, pass its ID as customer.id:
curl 'https://secure.payments.credova.com/payments' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'IDEMPOTENCY-KEY: 09ec2c87-7fb8-44ca-bb18-5c71a76974da' \
-H 'Content-Type: application/json' \
-d '{
"amount": 1000,
"currency": "USD",
"payment_method": {
"card_details": {
"cardholder_name": "John Smith",
"number": "4242424242424242",
"expiration_month": 12,
"expiration_year": 2030,
"cvc": "123"
}
},
"customer": {
"id": "cus_7Ay5mcUXAxwrN6wQEQUVEHBCJ"
},
"billing_details": {
"address_line_1": "111 Test St.",
"city": "Des Moines",
"state": "IA",
"postal_code": "51111",
"country": "US"
},
"device_information": {
"ip_address": "203.0.113.10"
}
}'
Verify a Card
To check a card without charging it, send a verification payment with card_details. Set amount to 0, capture to false and verification.type:
curl 'https://secure.payments.credova.com/payments' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'IDEMPOTENCY-KEY: 5f0c1f3e-2f5b-4d7e-9b7e-0b8d1c6f4a21' \
-H 'Content-Type: application/json' \
-d '{
"amount": 0,
"capture": false,
"verification": {
"type": "add_card"
},
"currency": "USD",
"payment_method": {
"card_details": {
"cardholder_name": "John Smith",
"number": "4242424242424242",
"expiration_month": 12,
"expiration_year": 2030,
"cvc": "123"
}
},
"customer": {
"id": "cus_7Ay5mcUXAxwrN6wQEQUVEHBCJ"
},
"billing_details": {
"address_line_1": "111 Test St.",
"city": "Des Moines",
"state": "IA",
"postal_code": "51111",
"country": "US"
},
"device_information": {
"ip_address": "203.0.113.10"
}
}'
Payment Response with Card Details
The response is the same as for a payment with a saved card. payment_method.card describes the card that the request saved:
{
"id": "pmt_2YKewBonG4tgk12MheY3PiHDy",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "test",
"status": "succeeded",
"transaction_id": "trx_95rvMJvAVeG68W4NtLdfkN3LG",
"amount": 1000,
"amount_capturable": 0,
"amount_charged": 1000,
"amount_refunded": 0,
"refunded": false,
"currency": "USD",
"payment_method": {
"card": {
"id": "card_AjkCFKAYiTsjghXWMzoXFPMxj",
"cardholder_name": "John Smith",
"last4": "4242",
"bin": "424242",
"exp_month": "12",
"exp_year": "2030",
"brand": "visa",
"avs_code": "Y",
"cvv2_reply": "M",
"fingerprint": "CC2XvyoohnqecEq4r3FtXv6MdCx4TbaW1UUTdCCN5MNL"
}
},
"customer": {
"id": "cus_7Ay5mcUXAxwrN6wQEQUVEHBCJ",
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com"
},
"billing_details": {
"address_line_1": "111 Test St.",
"city": "Des Moines",
"state": "IA",
"postal_code": "51111",
"country": "US"
},
"transaction": {
"id": "trx_95rvMJvAVeG68W4NtLdfkN3LG",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "test",
"status": "succeeded",
"amount": 1000,
"fee_amount": 0,
"net_amount": 1000,
"currency": "USD",
"type_id": "pmt_2YKewBonG4tgk12MheY3PiHDy",
"type": "payment",
"processor": "nuvei",
"processor_id": "7110000000010554769",
"created_at": "2024-06-30T01:02:29.212Z",
"modified_at": "2024-06-30T01:02:29.212Z"
},
"created_at": "2024-06-30T01:02:29.212Z",
"modified_at": "2024-06-30T01:02:29.212Z"
}
Store payment_method.card.id. For the next payment with this card, send "payment_method": { "card": "<card id>" } to https://api.publicsquare.com/payments, as in Accept Card Payments. A saved card is a token, so this request does not need the secure hostname.
Retries
Send an IDEMPOTENCY-KEY header with every request. If a request times out, send it again with the same key and the same card. The API returns the result of the first request and does not save the card again.
If you send the same key with a different card, the API returns 400 with an error on payment_method.card_details. Without a key, every retry saves the card again.
Errors
The secure hostname checks the card before the request reaches the Credova API:
| Status | Cause |
|---|---|
400 | A card field is missing or not valid. Each key in errors starts with payment_method.card_details., for example payment_method.card_details.number. |
400 | The card could not be turned into a token. The detail field gives the reason. |
404 | The request is not a POST to /payments or /payouts. |
502 | The secure hostname could not reach the card vault. Send the request again with the same IDEMPOTENCY-KEY. |
All other responses come from the Credova API. See Errors.
Conclusion
You created a payment with a card. To manage the payment or view its details, follow these guides: