Skip to main content

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.

Before you start​

Credova account
Sign up or sign in to the Credova Portal.
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.

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:

Create a Payment Intent
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"
}
}'
The amount is provided in cents. 1000 is the equivalent of $10.00
The IDEMPOTENCY-KEY header can be passed to protect against duplicate payment intents being created.
Create Response
{
"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:

Create a Payment Intent without a Payment Method
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"
}'
Create Response
{
"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:

Attach a Payment Method
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:

Confirm a Payment Intent
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:

Confirm Response — Succeeded (200)
{
"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:

Confirm Response — Processing (202)
{
"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"
}
When you receive a 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.
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",
"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:

Declined Payment Intent
{
"id": "pmt_int_2xNjK7abcdefghij",
"status": "requires_confirmation",
"last_payment_error": {
"code": "card_declined",
"message": "Your card was declined."
},
...
}

Pay with Card Details​

Your servers receive and send cardholder data in this flow, so your organization must be PCI certified. If you are unsure, please contact support. To keep card data out of your servers, save the card with Credova Elements and pay with a saved card.

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.

We recommend Payment Intents for new integrations. They are safe to retry and report slow processors by webhook. Payment Intents do not accept 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.

ModeHostnameAPI key
Livehttps://secure.payments.credova.comLive Secret Key
Testhttps://secure.payments.sbx.credova.comTest mode Secret Key
Use the hostname that matches the mode of your API key. Each hostname saves the card for its own mode, so a key from the other mode makes the request fail.

Put the card in payment_method.card_details:

FieldRequiredDescription
cardholder_nameYesName on the card.
numberYesCard number, digits only.
expiration_monthYesExpiration month, from 1 to 12.
expiration_yearYesFour-digit expiration year.
cvcNoCard 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​

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"
}
}'
The amount is provided in cents. 1000 is the equivalent of $10.00

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:

Create a Payment with Customer
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:

Verify a Card with Card Details
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:

Payment Response
{
"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:

StatusCause
400A card field is missing or not valid. Each key in errors starts with payment_method.card_details., for example payment_method.card_details.number.
400The card could not be turned into a token. The detail field gives the reason.
404The request is not a POST to /payments or /payouts.
502The 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: