TL;DR
The 2Checkout API (now Verifone) lets you process payments, manage subscriptions, and automate e-commerce workflows through REST endpoints for orders, customers, products, and webhooks.
This implementation guide covers authentication, payment and subscription flows, webhook verification, error handling, sandbox testing, and production readiness.
Introduction
Payment processing directly affects conversion and revenue. The 2Checkout API, now part of Verifone Digital Commerce, supports payment processing for merchants globally.
Payment friction matters: 67% of shoppers abandon carts due to payment issues. A reliable API integration helps reduce failures across checkout, renewals, refunds, and customer management.
By the end of this guide, you will have a practical implementation plan for:
- Authenticating API requests
- Creating one-time orders
- Managing customers and subscriptions
- Handling 3D Secure and multi-currency pricing
- Verifying and processing webhooks
- Testing in sandbox before production
Apidog can simplify API integration testing. Use it to test 2Checkout endpoints, validate webhook payloads, mock responses, and share test scenarios with your team.
What Is the 2Checkout API?
2Checkout, now operating as Verifone Digital Commerce, provides a RESTful API for payment processing and subscription management.
You can use it for:
- One-time and recurring payments
- Customer and product management
- Order lifecycle tracking
- Refund and dispute handling
- Tax and compliance automation
- Multi-currency support across 100+ currencies
Key Features
| Feature | Description |
|---|---|
| RESTful design | Standard GET, POST, PUT, and DELETE methods with JSON payloads |
| Sandbox environment | Test payments without processing real transactions |
| Webhook support | Receive real-time notifications for order events |
| Tokenization | Handle payment data without storing card details |
| Global compliance | PCI DSS Level 1, GDPR, PSD2, and 3D Secure 2.0 support |
API Architecture
2Checkout uses versioned REST API paths:
https://api.2checkout.com/1/
https://api.2checkout.com/2/
Version 2 is the current recommended version, with improved subscription management and webhook handling.
Getting Started: Authentication Setup
Step 1: Create a Merchant Account
Before calling the API:
- Visit the 2Checkout or Verifone signup page.
- Complete business verification.
- Wait for account approval, typically 24–48 hours.
- Open the Control Panel and retrieve your API credentials.
Step 2: Retrieve API Keys
In the Control Panel, go to Integrations > API Keys.
You need:
- Private API Key: Server-side authentication. Keep this secret.
- Public API Key: Client-side tokenization. Safe to expose in the browser.
- Webhook Secret: Verifies webhook signatures.
Store secrets in environment variables rather than source code:
# .env
TWOCHECKOUT_PRIVATE_KEY="your_private_key_here"
TWOCHECKOUT_PUBLIC_KEY="your_public_key_here"
TWOCHECKOUT_WEBHOOK_SECRET="your_webhook_secret_here"
Add .env files to .gitignore and configure equivalent secrets in your deployment platform.
Step 3: Choose Sandbox or Production
| Environment | Base URL | Use case |
|---|---|---|
| Sandbox | https://sandbox.2checkout.com/api/ |
Development and testing |
| Production | https://api.2checkout.com/ |
Live transactions |
Use sandbox credentials during development. Switch both the base URL and credentials when you are ready to accept real payments.
Step 4: Authenticate Requests
2Checkout supports API key authentication and HMAC request signing.
Method 1: API Key Authentication
Send the private key in the request headers:
const response = await fetch('https://api.2checkout.com/1/orders', {
method: 'GET',
headers: {
'X-Api-Key': process.env.TWOCHECKOUT_PRIVATE_KEY,
'Content-Type': 'application/json',
Accept: 'application/json'
}
});
Method 2: HMAC Signature Authentication
For enhanced security, sign request payloads with HMAC-SHA256:
const crypto = require('crypto');
function generateSignature(payload, privateKey) {
return crypto
.createHmac('sha256', privateKey)
.update(JSON.stringify(payload))
.digest('hex');
}
const payload = {
order_id: '12345',
amount: 99.99
};
const signature = generateSignature(
payload,
process.env.TWOCHECKOUT_PRIVATE_KEY
);
const response = await fetch('https://api.2checkout.com/1/orders', {
method: 'POST',
headers: {
'X-Api-Key': process.env.TWOCHECKOUT_PRIVATE_KEY,
'X-Signature': signature,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
Processing Payments: Core Endpoints
Create a One-Time Order
Use the /orders endpoint to create a payment order. Tokenize card details client-side and send only the resulting token from your server.
const createOrder = async (customerData, productData) => {
const payload = {
currency: 'USD',
customer: {
email: customerData.email,
first_name: customerData.firstName,
last_name: customerData.lastName,
phone: customerData.phone,
billing_address: {
address1: customerData.address,
city: customerData.city,
state: customerData.state,
zip: customerData.zip,
country: customerData.country
}
},
items: [
{
name: productData.name,
quantity: productData.quantity,
price: productData.price,
product_code: productData.sku
}
],
payment_method: {
type: 'card',
card_token: customerData.cardToken
}
};
const response = await fetch('https://api.2checkout.com/1/orders', {
method: 'POST',
headers: {
'X-Api-Key': process.env.TWOCHECKOUT_PRIVATE_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
return response.json();
};
Expected Response
{
"order_id": "ORD-2026-001234",
"status": "approved",
"amount": 99.99,
"currency": "USD",
"customer_id": "CUST-789456",
"transaction_id": "TXN-9876543210",
"created_at": "2026-03-20T10:30:00Z"
}
Handle Payment Errors
Check both API-level failures and payment-specific error responses.
try {
const result = await createOrder(customer, product);
if (result.error) {
switch (result.error.code) {
case 'CARD_DECLINED':
// Ask the customer to use another payment method.
break;
case 'INSUFFICIENT_FUNDS':
// Show an appropriate payment failure message.
break;
case 'INVALID_CVV':
// Ask the customer to re-enter their CVV.
break;
default:
console.error('Payment failed:', result.error);
}
}
} catch (error) {
// Network, timeout, or server error.
console.error('API request failed:', error);
}
Common Error Codes
| Error code | HTTP status | Description | Resolution |
|---|---|---|---|
CARD_DECLINED |
402 | Card was declined | Ask for another payment method |
INVALID_CARD |
400 | Invalid card number | Validate card input |
EXPIRED_CARD |
400 | Card has expired | Request updated expiration |
INVALID_CVV |
400 | CVV verification failed | Re-request CVV |
INSUFFICIENT_FUNDS |
402 | Not enough funds | Suggest an alternative payment method |
DUPLICATE_ORDER |
409 | Order already processed | Check idempotency and duplicate submissions |
INVALID_CURRENCY |
400 | Unsupported currency | Verify the currency code |
API_KEY_INVALID |
401 | Authentication failed | Check the API key |
Customer Management
Customer records are useful for repeat purchases and subscription billing.
Create a Customer
const createCustomer = async (customerData) => {
const payload = {
email: customerData.email,
first_name: customerData.firstName,
last_name: customerData.lastName,
phone: customerData.phone,
company: customerData.company,
billing_address: {
address1: customerData.address,
address2: customerData.address2 || '',
city: customerData.city,
state: customerData.state,
zip: customerData.zip,
country: customerData.country
},
shipping_address: customerData.shippingAddress || null,
tax_exempt: false,
language: 'en'
};
const response = await fetch('https://api.2checkout.com/1/customers', {
method: 'POST',
headers: {
'X-Api-Key': process.env.TWOCHECKOUT_PRIVATE_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
return response.json();
};
Customer Response
{
"customer_id": "CUST-2026-123456",
"email": "john.doe@example.com",
"first_name": "John",
"last_name": "Doe",
"created_at": "2026-03-20T10:00:00Z",
"updated_at": "2026-03-20T10:00:00Z",
"payment_methods": [],
"subscriptions": [],
"order_history": []
}
Retrieve a Customer
const getCustomer = async (customerId) => {
const response = await fetch(
`https://api.2checkout.com/1/customers/${customerId}`,
{
method: 'GET',
headers: {
'X-Api-Key': process.env.TWOCHECKOUT_PRIVATE_KEY,
'Content-Type': 'application/json'
}
}
);
return response.json();
};
Update a Customer
const updateCustomer = async (customerId, updates) => {
const response = await fetch(
`https://api.2checkout.com/1/customers/${customerId}`,
{
method: 'PUT',
headers: {
'X-Api-Key': process.env.TWOCHECKOUT_PRIVATE_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(updates)
}
);
return response.json();
};
Delete a Customer
const deleteCustomer = async (customerId) => {
const response = await fetch(
`https://api.2checkout.com/1/customers/${customerId}`,
{
method: 'DELETE',
headers: {
'X-Api-Key': process.env.TWOCHECKOUT_PRIVATE_KEY
}
}
);
return response.status === 204;
};
Note: Deleting a customer with active subscriptions or outstanding balances will fail. Cancel subscriptions first.
Advanced Integration Patterns
Use Idempotency Keys for Safe Retries
Payment requests must be safe to retry. Include a unique idempotency key for each order:
const createIdempotentOrder = async (payload, idempotencyKey) => {
const response = await fetch('https://api.2checkout.com/1/orders', {
method: 'POST',
headers: {
'X-Api-Key': process.env.TWOCHECKOUT_PRIVATE_KEY,
'Content-Type': 'application/json',
'X-Idempotency-Key': idempotencyKey
},
body: JSON.stringify(payload)
});
return response.json();
};
// Generate once per order and store it in your database.
const idempotencyKey = `order_${userId}_${Date.now()}`;
If a request times out after 2Checkout processes it, retry with the same key. The API should return the original result instead of creating a duplicate charge.
Handle 3D Secure 2.0
For European customers, 3D Secure 2.0 is mandatory under PSD2.
const createOrderWith3DS = async (payload) => {
const response = await fetch('https://api.2checkout.com/1/orders', {
method: 'POST',
headers: {
'X-Api-Key': process.env.TWOCHECKOUT_PRIVATE_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
...payload,
three_ds: {
enabled: true,
challenge_required: 'preferred',
notification_url: 'https://your-site.com/3ds-callback'
}
})
});
const result = await response.json();
if (result.three_ds_redirect_url) {
// Redirect the customer to their bank for authentication.
res.redirect(result.three_ds_redirect_url);
}
return result;
};
Use mandatory when required for EU flows.
Retrieve Multi-Currency Prices
You can display a localized price while settling in your base currency:
const getLocalizedPrice = async (basePrice, targetCurrency) => {
const response = await fetch(
`https://api.2checkout.com/1/rates?from=USD&to=${targetCurrency}`,
{
headers: {
'X-Api-Key': process.env.TWOCHECKOUT_PRIVATE_KEY
}
}
);
const rates = await response.json();
return basePrice * rates.rate;
};
const eurPrice = await getLocalizedPrice(99.99, 'EUR');
console.log(`Price: EUR ${eurPrice.toFixed(2)}`);
Apply Proration for Subscription Upgrades
When a customer changes plans mid-cycle, use proration settings to calculate the difference:
const upgradeSubscription = async (subscriptionId, newPlanId) => {
const response = await fetch(
`https://api.2checkout.com/1/subscriptions/${subscriptionId}/upgrade`,
{
method: 'POST',
headers: {
'X-Api-Key': process.env.TWOCHECKOUT_PRIVATE_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
plan_id: newPlanId,
proration: 'immediate',
invoice_proration: true
})
}
);
return response.json();
};
Subscription Management
2Checkout supports recurring billing workflows, including trials, renewals, upgrades, and cancellations.
Create a Subscription
const createSubscription = async (customerId, planId) => {
const payload = {
customer_id: customerId,
plan_id: planId,
start_date: new Date().toISOString(),
billing_cycle: 'monthly',
payment_method: {
type: 'card',
card_token: 'tok_card_tokenized'
},
options: {
trial_days: 14,
auto_renew: true
}
};
const response = await fetch(
'https://api.2checkout.com/1/subscriptions',
{
method: 'POST',
headers: {
'X-Api-Key': process.env.TWOCHECKOUT_PRIVATE_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
}
);
return response.json();
};
Subscription Response
{
"subscription_id": "SUB-2026-567890",
"status": "active",
"plan_id": "PLAN-PREMIUM-MONTHLY",
"customer_id": "CUST-789456",
"current_period_start": "2026-03-20T00:00:00Z",
"current_period_end": "2026-04-20T00:00:00Z",
"trial_end": "2026-04-03T00:00:00Z",
"amount": 29.99,
"currency": "USD"
}
Update a Subscription
Use PUT to change plans, quantities, or payment methods:
const updateSubscription = async (subscriptionId, updates) => {
const payload = {
...updates
// Example values:
// plan_id: 'PLAN-ENTERPRISE-MONTHLY',
// quantity: 5,
// payment_method: { card_token: 'new_token' }
};
const response = await fetch(
`https://api.2checkout.com/1/subscriptions/${subscriptionId}`,
{
method: 'PUT',
headers: {
'X-Api-Key': process.env.TWOCHECKOUT_PRIVATE_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
}
);
return response.json();
};
Cancel a Subscription
const cancelSubscription = async (subscriptionId, reason = '') => {
const payload = {
cancel_at_period_end: false,
reason
};
const response = await fetch(
`https://api.2checkout.com/1/subscriptions/${subscriptionId}/cancel`,
{
method: 'POST',
headers: {
'X-Api-Key': process.env.TWOCHECKOUT_PRIVATE_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
}
);
return response.json();
};
Set cancel_at_period_end to true to preserve customer access through the current billing period.
Webhook Integration: Real-Time Event Handling
Webhooks notify your application about payment and subscription events without polling. They are essential for renewals, failed payments, refunds, and access control.
Step 1: Configure a Webhook Endpoint
In the 2Checkout Control Panel:
- Go to Integrations > Webhooks.
- Add an HTTPS endpoint URL.
- Select the events your application needs.
- Save the configuration.
- Store the generated webhook secret securely.
Step 2: Implement a Webhook Handler
Use the raw request body for signature verification.
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post(
'/webhooks/2checkout',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payload = req.body;
const isValid = verifyWebhookSignature(
payload,
signature,
process.env.TWOCHECKOUT_WEBHOOK_SECRET
);
if (!isValid) {
console.error('Invalid webhook signature');
return res.status(401).send('Unauthorized');
}
const event = JSON.parse(payload.toString());
switch (event.type) {
case 'order.created':
await handleOrderCreated(event.data);
break;
case 'order.approved':
await handleOrderApproved(event.data);
break;
case 'order.declined':
await handleOrderDeclined(event.data);
break;
case 'subscription.created':
await handleSubscriptionCreated(event.data);
break;
case 'subscription.renewed':
await handleSubscriptionRenewed(event.data);
break;
case 'subscription.cancelled':
await handleSubscriptionCancelled(event.data);
break;
case 'refund.processed':
await handleRefundProcessed(event.data);
break;
default:
console.log('Unhandled event type:', event.type);
}
return res.status(200).send('OK');
}
);
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
Critical Webhook Events
| Event type | Trigger | Recommended action |
|---|---|---|
order.created |
New order placed | Send a confirmation email |
order.approved |
Payment successful | Fulfill the order or grant access |
order.declined |
Payment failed | Notify the customer and apply retry logic |
subscription.renewed |
Recurring payment completed | Extend the access period |
subscription.payment_failed |
Renewal failed | Start a dunning sequence |
subscription.cancelled |
Customer canceled | Revoke access at period end |
refund.processed |
Refund issued | Update user balance |
chargeback.received |
Dispute filed | Gather evidence |
Webhook Best Practices
- Verify signatures: Prevent spoofed webhook requests.
-
Return
200 OKquickly: Non-200 responses can trigger retries. - Process asynchronously: Queue heavy work instead of blocking the webhook response.
- Make handlers idempotent: Duplicate deliveries can happen.
- Log every event: Include event IDs, timestamps, payload metadata, and processing status.
Testing Your Integration
Use the Sandbox Environment
Use sandbox endpoints and keys before processing real payments:
const BASE_URL = 'https://sandbox.2checkout.com/api/1';
const TEST_CARDS = {
APPROVED: '4111111111111111',
DECLINED: '4000000000000002',
INSUFFICIENT_FUNDS: '4000000000009995',
EXPIRED_CARD: '4000000000000069'
};
const TEST_ADDRESS = {
country: 'US',
zip: '90210'
};
Test success paths and failure paths before production:
- Approved payments
- Declined cards
- Invalid CVV values
- Insufficient funds
- Expired cards
- Subscription renewals and cancellation
- Webhook retries and duplicate deliveries
Test Webhooks Locally
Use ngrok to expose a local webhook handler:
# Install ngrok
npm install -g ngrok
# Start your application
node server.js
# Expose local port 3000
ngrok http 3000
Copy the generated HTTPS URL into the 2Checkout webhook settings.
Test With Apidog
Apidog can streamline testing for a 2Checkout integration:
- Import the OpenAPI specification
- Create endpoint-specific test scenarios
- Mock API responses without calling the live API
- Validate webhook payload structures
- Share collections and environments with your team
Create separate sandbox and production environments, then switch API base URLs and credentials without changing requests.
Troubleshooting Common Issues
Webhooks Are Not Arriving
Symptoms: Orders process successfully, but your application state does not update.
Check webhook delivery logs in the 2Checkout dashboard for failed attempts and non-200 responses.
Fixes:
- Return
200 OKwithin five seconds. - Confirm the endpoint uses HTTPS.
- Validate the SSL certificate.
- Whitelist 2Checkout IP ranges in your firewall.
- Review signature verification logic.
- Test the endpoint with a webhook simulator before production.
Sandbox Payments Fail
Symptoms: All test cards decline in sandbox.
Check the following:
- Use sandbox API keys, not production keys.
- Use the sandbox base URL:
https://sandbox.2checkout.com/api/. - Use supported test card numbers.
- Verify that the sandbox account is active.
Subscription Renewals Fail Silently
Symptoms: A subscription remains active, but renewal payments do not process.
Query payment history:
const history = await fetch(
`https://api.2checkout.com/1/subscriptions/${subId}/payments`,
{
headers: {
'X-Api-Key': privateKey
}
}
);
Then verify:
- The customer payment method has not expired.
- Dunning settings are enabled in the Control Panel.
- Your application receives
subscription.payment_failedevents. - The subscription has
auto_renewenabled.
Currency Conversion Does Not Match Expected Values
Symptoms: The charged amount differs from a locally calculated conversion.
2Checkout uses daily exchange rates, which fluctuate.
Recommended approach:
- Display conversion amounts as approximate.
- Lock rates at cart creation with a 15-minute expiry.
- Store transactions in the customer’s local currency.
AVS Failures
Symptoms: Valid cards fail because the billing address does not match.
Possible improvements:
- Add address autocomplete with tools such as Google Places or Lob.
- Require ZIP or postal code during checkout.
- Use soft AVS warnings instead of immediate declines.
- Let customers update their billing address easily.
Production Deployment Checklist
Before going live:
- [ ] Switch from sandbox to production API keys.
- [ ] Update the base URL to
https://api.2checkout.com/. - [ ] Enable webhook signature verification.
- [ ] Monitor failed payments.
- [ ] Add retry handling for transient failures.
- [ ] Test refund and chargeback flows.
- [ ] Verify PCI DSS compliance by using tokenization.
- [ ] Enable 3D Secure 2.0 for EU customers.
- [ ] Create timestamped audit logs.
- [ ] Write a runbook for payment incidents.
Monitor Payment Health
Track payment success rates and error-code trends.
const successRate = (approvedOrders / totalOrders) * 100;
if (successRate < 95) {
sendAlert('Payment success rate dropped below 95%');
}
const errorBreakdown = errors.reduce((acc, err) => {
acc[err.code] = (acc[err.code] || 0) + 1;
return acc;
}, {});
if (errorBreakdown.CARD_DECLINED > threshold) {
sendAlert('Spike in card declines detected');
}
Useful production metrics include:
- Approval rate
- Checkout error rate
- Webhook delivery failures
- Subscription renewal success rate
- Refund volume
- Chargeback volume
- Error-code distribution
Real-World Use Cases
E-Commerce Store Integration
A fashion retailer integrated 2Checkout for global payments and reported:
- Support for 100+ currencies
- A 23% reduction in cart abandonment
- Automated EU VAT compliance
- More than $2M processed in the first year
The team started with 2Checkout hosted checkout pages, then moved to direct API integration for a custom checkout experience. Implementation took three weeks.
SaaS Subscription Business
A project-management SaaS used 2Checkout subscriptions to:
- Manage more than 5,000 active subscriptions
- Apply proration for plan upgrades
- Automate dunning for failed renewals
- Reduce churn by 15% through smart retries
The key pattern was webhook-driven access control:
- On
subscription.renewed, extend user access immediately. - On
subscription.cancelled, schedule access revocation.
Conclusion
The 2Checkout API provides the core building blocks for payment processing and subscription management.
For a production-ready integration:
- Develop and test in sandbox first.
- Keep API keys and webhook secrets out of source control.
- Use tokenization instead of handling raw card data.
- Add idempotency to payment requests and webhook handlers.
- Verify webhook signatures with HMAC-SHA256.
- Test trial, renewal, cancellation, refund, and failure flows.
- Monitor payment success rates and error-code spikes.
- Use Apidog to organize API tests, mock responses, and validate webhook payloads.
FAQ
What is the 2Checkout API?
The 2Checkout API, now Verifone, is a RESTful interface for processing payments, managing subscriptions, handling refunds, and automating e-commerce transactions. It supports JSON payloads, HMAC authentication, and real-time webhooks.
Is 2Checkout the same as Verifone?
Yes. Verifone acquired 2Checkout in 2020 and rebranded it as Verifone Digital Commerce. API endpoints and functionality remain the same, although some documentation uses Verifone branding.
How do I get a 2Checkout API key?
Log in to the 2Checkout Control Panel, go to Integrations > API Keys, and generate a key. You receive a private key for server-side calls and a public key for client-side tokenization.
Does 2Checkout have a sandbox environment?
Yes. Use https://sandbox.2checkout.com/api/ for testing. Create a separate sandbox account and use sandbox API keys to process test transactions without real charges.
What payment methods does 2Checkout support?
2Checkout supports credit cards including Visa, Mastercard, Amex, and Discover; PayPal; Apple Pay; Google Pay; bank transfers; and local payment methods across 100+ countries.
How do I handle webhooks securely?
Verify the X-Webhook-Signature header with HMAC-SHA256 and your webhook secret. Process events asynchronously and return 200 OK immediately to avoid unnecessary retries.
What happens when a subscription payment fails?
2Checkout sends a subscription.payment_failed webhook. Implement retry logic, typically three attempts over seven days, and handle subscription.cancelled if all retries fail.
Is 2Checkout PCI DSS compliant?
Yes. 2Checkout is PCI DSS Level 1 certified. Use client-side tokenization to avoid handling raw card data and reduce your PCI compliance scope.
Can I test subscriptions in sandbox?
Yes. Sandbox supports subscription lifecycle testing, including trials, renewals, upgrades, downgrades, and cancellations. Use test card 4111111111111111 for successful payments.
How do I handle refunds through the API?
Send a POST request to /refunds with the order ID and refund amount. 2Checkout supports partial and full refunds and sends a refund.processed webhook when processing completes.

Top comments (0)