Save a card for future transactions
How to save a customer's card on file for use in future purchases or recurring payments.
What is card-on-file (COF)?
Card-on-file (COF) practices are governed by card networks and are designed to enhance security and transparency for consumers. A key compliance requirement under PCI-DSS and card network rules is that merchants must obtain explicit consent from cardholders before storing and using their payment credentials for future transactions.
COF transactions occur when cardholders authorize merchants to retain their card information for future purchases or recurring payments. To build and test the COF feature, you can use sandbox test cards.
Before you begin
Note the following:
- Cardholder consent—Merchants must obtain cardholder consent before saving a card on file.
- Tokenization—Ensure multi-pay tokens are enabled in your Clover gateway settings. Generate a card token using the Ecommerce API or iframe, then use it to save the card.
- Automated notifications—When a COF record is saved, Clover automatically sends email and SMS alerts for upcoming charges, failed payments, and subscription renewals.
- COF removal on request—Cardholders can request deletion of their saved card data.
- External token management—If not using automated flows from Clover, you can manage multi-pay tokens and customer data externally, including customer profiles, stored credentials, and payment details. For more information, see Use external multi-pay tokens with Clover Ecommerce APIs.
Workflow

Card-on-file payment flow
Prerequisites
- Generate an Ecommerce API key (PAKMS key) or
apiAccessKey
. - Use the PAKMS key to create a card token. You must have all the required details, such as the card number, card verification value (CVV), and expiration date.
Save a card-on-file for a new customer
- Send a POST request to
/v1/customers
. - Enter the required information:
email
—Customer's email address.source
—Card token to charge. Example:clv_1ABCDefgHI23jKL4m5nOPqR
, generated from the create a card token/v1/tokens
endpoint.
- Set the Authorization header as Bearer token type, and enter the test API token or OAuth-generated
access_token
.
Request and response example—Save card-on-file for a new customer
curl --request POST \
--url https://scl-sandbox.dev.clover.com/v1/customers \
--header 'Accept: application/json' \
--header 'Authorization: Bearer ab86a5e8-48f3-b3bd-8c45-d415e9867833' \
--header 'Content-Type: application/json' \
--data '{
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe",
"source": "clv_1TSTSwVSFJxGwWKzb79Sderj"
}'
{
"id": "PWG98TV2ECTT2", //customerId
"object": "customer",
"created": 1748340989108,
"currency": "USD",
"email": "[email protected]",
"name": " ",
"sources": {
"object": "list",
"data": [
"8YTCARDXSQHP0"
]
},
"shipping": {
"name": " "
}
}
In response, Clover:
- Generates a unique
customerId
. - Sets the card
source
token as a multi-pay token for the customer.
From here, you can use either the customerId
or the multi-pay card token to create a charge for submitting payments with a customer's COF. See Make subsequent payments with saved cards.
Save a new card for an existing customer
If the cardholder requests removal of the card-on-file (VOF) or if the customer's payment method changes, you must revoke the existing (COF) and add a new one. To save a new card for an existing customer, you need to complete two steps: 1. Revoke the existing card, and 2. Add the new card information.
Step 1: Revoke the existing card
- Send a DELETE request to
/v1/customers/{customerId}sources/{cardId}
. - Set the Authorization header as Bearer token type, and enter the test API token or OAuth-generated
access_token
.
curl --request DELETE \
--url 'https://scl-sandbox.dev.clover.com/v1/customers/{customerId}/sources/{cardId}' \
--header 'accept: application/json' \
--header 'authorization: Bearer {accessToken}'\
Step 2: Add new card information to the existing customer record
- Send a PUT request to
/v1/customers/{customerId}
. - Enter information in the required fields:
source
—Payment source or card token to charge. Example:clv_1ABCDefgHI23jKL4m5nOPqR
generated from the create a card token/v1/tokens
endpoint.customerId
—Customer identifier.email address
—Used to notify the customers that their card data is saved to their profile.
- Set the Authorization header as Bearer token type, and enter the test API token or OAuth-generated
access_token
.
curl --request PUT \
--url 'https://scl-sandbox.dev.clover.com/v1/customers/{customerId}' \
--header 'accept: application/json' \
--header 'authorization: Bearer {access_token}' \
--header 'content-type: application/json' \
--data '{"source":"{token}"}'
Make payments with saved cards
You can make payments using either the customerId or a specific card token.
- Use
customerId
if the customer has only one saved card. - Use a card token if the customer has multiple cards. In this case, include the
stored_credentials
object.
Create a charge with customerId
customerId
- Send a POST request to the
v1/charges
endpoint to create a charge. - Enter the required parameters in the request:
amount
currency
source
—Enter thecustomerId
generated when you save a card-on-file for a new customer.x-forwarded-for
—Client internet protocol (IP) address of the web browser from which the customer’s payment originates.
- Set the Authorization header as Bearer token type, and enter the test API token or OAuth-generated
access_token
.
Request example—Create a subsequent charge with a customer ID
curl --request POST \
--url 'https://scl-sandbox.dev.clover.com/v1/charges' \
--header 'accept: application/json' \
--header 'authorization: Bearer {access_token}' \
--header 'idempotency-key: {uuid4_key}' \
--header 'content-type: application/json' \
--header 'x-forwarded-for: {client_ip}' \
--data '{
"amount": 30,
"currency": "usd",
"source": "{customer_id}"
}'
Create a charge with a multi-pay card token
When you use a multi-pay card token generated from the v1/customers
endpoint to create a charge, for subsequent payments, you must also include the required stored_credentials
values in the charge request.
- Send a POST request to the
v1/charges
endpoint to create a charge. - Enter the required parameters in the request:
amount
in centscurrency
source
—Enter thecustomerId
generated when you save a card-on-file for a new customer.sequence
—Indicates the sequence of the transaction for the same payment card, whether First or Subsequent.is_scheduled
—Indicates whether the transaction is scheduled or part of an installment.
Note: Installments are only available in the US.initiator
—Indicates whether the transaction is initiated by the merchant or with the cardholder's consent.
- Optional. In case of installments, enter information in the
installment_info
object.
bill_pay_indicator
—Indicates whether the transaction is a recurring or installment payment.invoice_number
—Invoice number of the installment or recurring transaction.description
—Description of theinstallment_info
or recurring transaction.
- Set the Authorization header as Bearer token type, and enter the test API token or OAuth-generated
access_token
.
Request example—Create initial charge with a multi-pay card token
curl --request POST \
--url 'https://scl-sandbox.dev.clover.com/v1/charges' \
--header 'accept: application/json' \
--header 'authorization: Bearer {access_token}' \
--header 'idempotency-key: {uuid4_key}' \
--header 'content-type: application/json' \
--header 'x-forwarded-for: {client_ip}' \
--data '{
"ecomind": "ecom",
"metadata": {
"existingDebtIndicator": false
},
"stored_credentials": {
"sequence": "FIRST",
"is_scheduled": false,
"initiator": "MERCHANT"
},
"amount": 25,
"currency": "USD",
"source": "clv_1TSTS6mpb4FHAoLqPM9vUC66"
}'
{
"id": "J2RK5QKH6WCYE",
"amount": 25,
"payment_method_details": "card",
"amount_refunded": 0,
"currency": "USD",
"created": 1748341370775,
"captured": true,
"ref_num": "514700500080",
"auth_code": "OK8320",
"outcome": {
"network_status": "approved_by_network",
"type": "authorized"
},
"paid": true,
"status": "succeeded",
"source": {
"id": "clv_1TSTS6mpb4FHAoLqPM9vUC66",
"brand": "VISA",
"exp_month": "12",
"exp_year": "2030",
"first6": "424242",
"last4": "4242"
},
"ecomind": "ecom"
}
Request and response example—Pay for an order with an initial charge
curl --request POST \
--url 'https://scl-sandbox.dev.clover.com/v1/orders/{orderId}/pay' \
--header 'accept: application/json' \
--header 'authorization: Bearer {access_token}' \
--header 'idempotency-key: {uuid4_key}' \
--header 'content-type: application/json' \
--header 'x-forwarded-for: {client_ip}' \
--data '{
"ecomind": "ecom",
"stored_credentials": {
"sequence": "FIRST",
"is_scheduled": false,
"initiator": "MERCHANT"
},
"customer": "PWG98TV2ECTT2",
"amount": 25,
"currency": "USD"
}'
{
"id": "4F3DBC6ZNEWQJ",
"object": "order",
"amount": 25,
"amount_paid": 25,
"currency": "USD",
"charge": "3H1K9CBHNQGCT",
"created": 1748341777000,
"customer": "PWG98TV2ECTT2",
"ref_num": "514700500090",
"auth_code": "OK2910",
"items": [
{
"quantity": 1,
"amount": 25
}
],
"source": {
"brand": "VISA",
"exp_month": "12",
"exp_year": "2030",
"first6": "424242",
"last4": "4242"
},
"status": "paid",
"status_transitions": {
"paid": 1748341867575
},
"ecomind": "ecom"
}
Request example—Create a subsequent charge with a multi-pay card token
curl --request POST \
--url 'https://scl-sandbox.dev.clover.com/v1/charges' \
--header 'accept: application/json' \
--header 'authorization: Bearer {access_token}' \
--header 'idempotency-key: {uuid4_key}' \
--header 'content-type: application/json' \
--header 'x-forwarded-for: {client_ip}' \
--data '{
"ecomind": "ecom",
"metadata": {
"existingDebtIndicator": false
},
"stored_credentials": {
"sequence": "SUBSEQUENT",
"is_scheduled": false,
"initiator": "CARDHOLDER"
},
"amount": 25,
"currency": "USD",
"source": "clv_1TSTS6mpb4FHAoLqPM9vUC66"
}'
{
"id": "PQ7XWMJ4Y4P1Y",
"amount": 25,
"payment_method_details": "card",
"amount_refunded": 0,
"currency": "USD",
"created": 1748341529329,
"captured": true,
"ref_num": "514700500085",
"auth_code": "OK9930",
"outcome": {
"network_status": "approved_by_network",
"type": "authorized"
},
"paid": true,
"status": "succeeded",
"source": {
"id": "clv_1TSTS6mpb4FHAoLqPM9vUC66",
"brand": "VISA",
"exp_month": "12",
"exp_year": "2030",
"first6": "424242",
"last4": "4242"
},
"ecomind": "ecom"
}
Request example—Pay for an order with a subsequent charge
curl --request POST \
--url 'https://scl-sandbox.dev.clover.com/v1/orders/{orderId}/pay' \
--header 'accept: application/json' \
--header 'authorization: Bearer {access_token}' \
--header 'content-type: application/json' \
--header 'x-forwarded-for: {client_ip}' \
--data '{
"source": "{multi_pay_token}",
"email": "[email protected]",
"stored_credentials": {
"sequence": "SUBSEQUENT",
"is_scheduled": false,
"initiator": "CARDHOLDER"
}
}'
{
"id": "XAQXQZNW25AB0",
"amount": 35,
"payment_method_details": "card",
"currency": "USD",
"charge": "ZDTE58PPN05SE",
"created": 1588633964000,
"ref_num": "012500500550",
"auth_code": "OK0504",
"items": [
{
"quantity": 1,
"amount": 35,
"description": "item1"
}
],
"status": "paid",
"status_transitions": {
"paid": 1588634057577
},
"stored_credentials": {
"sequence": "SUBSEQUENT",
"initiator": "CARDHOLDER"
}
}
Possible responses
The following table describes the possible responses when running an endpoint in the Clover Ecommerce services APIs. These responses include various status codes and error messages that can help you diagnose and handle issues effectively during API integration.
Message | Description | Variable | Description |
---|---|---|---|
200 | string | Successful response. Returns the charge object with any captured value set to true. | |
400 Bad Request | string | charge | Indicates a card-related error. Provides the unique identifier of the failed charge. |
400 Bad Request | string | code | Provides additional information about the error to help users identify the issue. |
400 Bad Request | string | decline_code | Indicates a card issuer declined the transaction, including the reason if specified by the issuer. |
400 Bad Request | string | doc_url | URL (link) for more information about the reported error code. |
400 Bad Request | string | message | Detailed information about the error code. For card-related errors, this can provide more information to users. |
400 Bad Request | string | param | Lists the specific parameter related to the error. Useful for informing users about specific issues with their card information entry. |
400 Bad Request | string | type | Returned error type: - api_connection_error - api_error - authentication_error - card_error - idempotency_error - invalid_request_error - rate_limit_error |
Related topics
- Generate a PAKMS key tutorial
- Create a card token tutorial
- Use external multi-pay tokens with Clover Ecommerce APIs tutorial
- Create a charge tutorial
- Create a card-on-file customer endpoint
- Revoke a payment source endpoint
- Update a customer endpoint
- Create a charge endpoint
- Create an order endpoint
- Pay for an order endpoint
Updated 17 days ago