Card-on-file payments
Card-on-file (COF) is a payment method where a customer's card or wallet information is securely stored by the merchant or payment processor for future use, allowing seamless recurring transactions without the customer re-entering details each time.
This simplifies checkout, improves conversion, and supports subscription services, one-click purchases, and recurring billing — while keeping security via tokenization and compliance with payment industry standards.
Supported payment methods
| Payment method | Type | Description |
|---|---|---|
debit_credit_card | CARD | Secure storage of card details for subsequent transactions |
Only transactions completed with these payment methods are eligible for tokenization.
Prerequisites
Card-on-file requires the Customer module — every COF token is linked to a specific customer and can only be charged for transactions belonging to that customer.
Two ways to get started:
A. Create the customer first, then create a card-on-file transaction (2 API calls)
Create the customer via the Create Customer API. Then pass customerId when creating the card-on-file transaction to capture the initial card details for tokenization.
B. Create the customer during the card-on-file transaction (1 API call)
Capture customer details and the first card payment in a single request via Create Transaction (v2):
{
"amount": 100,
"currency": "GBP",
"tokenizationDetails": {
"tokenize": true,
"paymentType": "UNSCHEDULED",
"recurringFrequency": "UNSCHEDULED"
},
"customer": {
"name": "John Doe",
"email": "john.doe@example.com",
"billingEmail": "accounting@example.com",
"billingAddress1": "1 Main Street",
"billingAddress2": "Penthouse",
"billingCity": "Gotham",
"billingCountry": "TH",
"billingPostCode": "W3 ABC"
},
"customerAsPayer": true,
"webhook": "https://my-incoming-url.com/notifications-from-payments"
}
NodeJS:
const axios = require('axios');
const data = {
amount: 100,
currency: 'GBP',
tokenizationDetails: {
tokenize: true,
paymentType: 'UNSCHEDULED',
recurringFrequency: 'UNSCHEDULED',
},
customer: {
name: 'John Doe',
email: 'john.doe@example.com',
billingEmail: 'accounting@example.com',
billingAddress1: '1 Main Street',
billingAddress2: 'Penthouse',
billingCity: 'Gotham',
billingCountry: 'TH',
billingPostCode: 'W3 ABC',
},
customerAsPayer: true,
webhook: 'https://my-incoming-url.com/notifications-from-payments',
};
axios
.post('https://api.pomelopay.com/public/v2/transactions', data)
.then((res) => console.log('Response:', res.data))
.catch((err) => console.error('Error:', err));
PHP:
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$data = [
'amount' => 100,
'currency' => 'GBP',
'tokenizationDetails' => [
'tokenize' => true,
'paymentType' => 'UNSCHEDULED',
'recurringFrequency' => 'UNSCHEDULED',
],
'customer' => [
'name' => 'John Doe',
'email' => 'john.doe@example.com',
'billingAddress1' => '1 Main Street',
'billingCity' => 'Gotham',
'billingCountry' => 'TH',
'billingPostCode' => 'W3 ABC',
],
'customerAsPayer' => true,
'webhook' => 'https://my-incoming-url.com/notifications-from-payments',
];
$client = new Client();
$response = $client->post(
'https://api.pomelopay.com/public/v2/transactions',
['json' => $data],
);
echo $response->getBody();
Tokenization parameters
When creating the first transaction that captures card details, set the following on tokenizationDetails:
| Field | Value | Description |
|---|---|---|
tokenize | true | Indicates Pomelo should store the card details |
paymentType | UNSCHEDULED | Fixed value — merchant-initiated recurring payments |
recurringFrequency | UNSCHEDULED | Fixed value — ad-hoc payments only |
Example with an existing customer:
{
"amount": 100,
"currency": "GBP",
"tokenizationDetails": {
"tokenize": true,
"paymentType": "UNSCHEDULED",
"recurringFrequency": "UNSCHEDULED"
},
"customerId": "60f15d0d26f6d1a842bbd70a",
"customerAsPayer": true,
"webhook": "https://my-incoming-url.com/notifications-from-payments"
}
After the customer completes the payment, their card details are tokenized and stored.
Listing tokens for a customer
Get all stored cards for a customer via the Get Customer Token List API:
NodeJS:
const axios = require('axios');
const customerId = '60f15d0d26f6d1a842bbd70a';
axios
.get(`https://api.pomelopay.com/public-customers/${customerId}/tokens`)
.then((res) => console.log('Response:', res.data))
.catch((err) => console.error('Error:', err));
PHP:
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$customerId = '60f15d0d26f6d1a842bbd70a';
$client = new Client();
$response = $client->get("https://api.pomelopay.com/public-customers/{$customerId}/tokens");
echo $response->getBody();
Response shape:
{
"items": [
{
"defaultToken": true,
"deleted": false,
"_id": "652e642589dc860008b38500",
"token": "b9c6522baa0f6f15dcf8d48d064357c6:e38c3a217ee7cc6fb190ff13431d4421...",
"tokenAgreementType": "UNSCHEDULED",
"tokenType": "CARD",
"brand": "Master",
"paddedCardNumber": "5444******5118",
"tokenExpiryMonth": "12",
"tokenExpiryYear": "2025",
"customerId": "651e62e9d5d5c900086366cf",
"createdAt": "2023-10-17T10:38:29.097Z",
"id": "652e642589dc860008b38500"
}
],
"count": 1
}
Use the brand and paddedCardNumber to render a "saved cards" picker for your customer, or pick a token via business logic on your side.
Charging a token
To charge without re-collecting card details, first create a transaction for that customer via Create Transaction (v2):
{
"amount": 200,
"currency": "GBP",
"customerId": "651e62e9d5d5c900086366cf"
}
Then call Charge Stored Token in one of three ways:
Option 1 — by token id
{
"customerId": "651e62e9d5d5c900086366cf",
"transactionId": "652f4b1477b8290008d94996",
"tokenId": "652e642589dc860008b38500"
}
const axios = require('axios');
const data = {
customerId: '651e62e9d5d5c900086366cf',
transactionId: '652f4b1477b8290008d94996',
tokenId: '652e642589dc860008b38500',
};
axios
.post('https://api.pomelopay.com/public-customers/charge', data)
.then((res) => console.log('Response:', res.data));
Option 2 — by token value
{
"customerId": "651e62e9d5d5c900086366cf",
"transactionId": "652f4b1477b8290008d94996",
"token": "b9c6522baa0f6f15dcf8d48d064357c6:e38c3a217ee7cc6fb190ff13431d4421..."
}
Option 3 — without specifying a token (default token used)
{
"customerId": "651e62e9d5d5c900086366cf",
"transactionId": "652f4b1477b8290008d94996"
}
After charging the token, query Get Transaction to confirm state === "CONFIRMED". Or subscribe via webhooks for live updates.
Webhook events for tokenization
If you don't want to poll, set a webhook URL when creating the transaction. Pomelo POSTs JSON updates directly:
{
"amount": 100,
"currency": "GBP",
"webhook": "https://my-incoming-url.com/notifications-from-payments"
}
NOTIFY_TOKENISATION_STATUS event
Triggered after the system stores card details. Use customerId to query Get Customer Token List and obtain the token.
{
"created": "2023-10-17T10:38:29.215Z",
"deleted": false,
"eventType": "NOTIFY_TOKENISATION_STATUS",
"customerId": "651e62e9d5d5c900086366cf",
"transactionId": "652e60a8a30e140008a30675",
"tokenisationStatus": "TOKENISATION_SUCCESS",
"id": "652e642589dc860008b38501"
}
NOTIFY_TRANSACTION_CHANGE event
Triggered after any transaction state change (CONFIRMED, CANCELLED, FAILED). This is what you watch for after a Charge Token call.
{
"created": "2023-10-17T13:32:30.389Z",
"deleted": false,
"eventType": "NOTIFY_TRANSACTION_CHANGE",
"transactionId": "652e8cdc592bdb0008b91905",
"state": "CONFIRMED",
"amount": 5320,
"amountFractional": 53.2,
"amountFormatted": "GBP 53.20",
"currency": "GBP",
"id": "652e8cee9bc5f1000818e4f9"
}
For full details on webhook setup and signature verification, see the Webhooks guide.