TL;DR
The 2Checkout API (now Verifone) lets you programmatically process payments, manage subscriptions, and handle e-commerce transactions. It provides RESTful endpoints for orders, customers, products, and webhooks, using JSON-based authentication with API keys. This guide covers everything from setup to advanced webhook handling.
Introduction
Payment processing is critical for online businesses. A smooth integration increases revenue and unlocks global markets. The 2Checkout API (now Verifone Digital Commerce) is used by 45,000+ merchants and processes billions annually.
67% of shoppers abandon carts due to payment friction. A robust payment API integration can directly improve conversion rates.
This guide walks through a complete 2Checkout API integration: authentication, payment processing, subscription management, webhook handling, and error troubleshooting. Follow these steps to achieve a production-ready payment solution.
💡 Apidog streamlines API integration testing. Use it to test 2Checkout endpoints, validate webhook payloads, and debug authentication. Import the 2Checkout OpenAPI spec, mock responses, and share scenarios with your team.
What Is 2Checkout API?
2Checkout (now Verifone Digital Commerce) provides a RESTful API for:
- One-time and recurring payments
- Customer and product management
- Order lifecycle tracking
- Refund and dispute handling
- Tax and compliance automation
- Multi-currency support (100+ currencies)
Key Features
| Feature | Description |
|---|---|
| RESTful Design | Standard HTTP methods (GET, POST, PUT, DELETE) with JSON payloads |
| Sandbox Environment | Test payments without processing real transactions |
| Webhook Support | Real-time notifications for order events |
| Tokenization | Secure payment data handling without storing card details |
| Global Compliance | PCI DSS Level 1, GDPR, PSD2, and 3D Secure 2.0 |
API Architecture Overview
2Checkout uses a versioned REST API:
https://api.2checkout.com/1/
https://api.2checkout.com/2/
Version 2 is recommended for improved subscription management and webhook handling.
Getting Started: Authentication Setup
Step 1: Create Your 2Checkout Account
- Go to the 2Checkout (Verifone) signup page.
- Complete business verification (upload required documents).
- Wait for approval (24–48 hours typical).
- Access the Control Panel to retrieve API credentials.
Step 2: Retrieve API Keys
Navigate to Integrations > API Keys in your Control Panel:
- Private API Key: For server-side authentication (keep secret).
- Public API Key: For client-side tokenization (safe to expose).
- Webhook Secret: For webhook signature verification.
Security: Never commit API keys to version control. Use environment variables:
# .env file
TWOCHECKOUT_PRIVATE_KEY="your_private_key_here"
TWOCHECKOUT_PUBLIC_KEY="your_public_key_here"
TWOCHECKOUT_WEBHOOK_SECRET="your_webhook_secret_here"
Step 3: Sandbox vs 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 for development. Switch to production keys only when ready for real payments.
Step 4: Authentication Methods
Method 1: API Key Authentication (Recommended)
Add your private key to the request header:
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
Sign requests with HMAC-SHA256 for enhanced security:
const crypto = require('crypto');
function generateSignature(payload, privateKey) {
const hash = crypto
.createHmac('sha256', privateKey)
.update(JSON.stringify(payload))
.digest('hex');
return hash;
}
// Usage
const payload = { order_id: '12345', amount: 99.99 };
const signature = generateSignature(payload, privateKey);
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
Creating a One-Time Order
Create a single payment via the /orders endpoint:
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 // From client-side tokenization
}
};
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 await 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"
}
Handling Payment Errors
Always implement granular error handling:
try {
const result = await createOrder(customer, product);
if (result.error) {
switch (result.error.code) {
case 'CARD_DECLINED':
// Prompt for a different card
break;
case 'INSUFFICIENT_FUNDS':
// Show an appropriate message
break;
case 'INVALID_CVV':
// Request CVV re-entry
break;
default:
// Log and show generic error
console.error('Payment failed:', result.error);
}
}
} catch (error) {
// Network 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 different payment |
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 alternative payment |
DUPLICATE_ORDER |
409 | Order already processed | Check for duplicates |
INVALID_CURRENCY |
400 | Unsupported currency | Verify currency code |
API_KEY_INVALID |
401 | Authentication failed | Check API key |
Customer Management
Leverage the customer API for subscription businesses or repeat purchases.
Creating 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 await response.json();
};
Customer Response Example
{
"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": []
}
Retrieving Customer Details
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 await response.json();
};
Updating Customer Information
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 await response.json();
};
Deleting 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 customers with active subscriptions or balances will fail. Cancel subscriptions first.
Advanced Integration Patterns
Idempotency for Safe Retries
Support idempotent requests to avoid duplicate charges:
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 // Unique per order
},
body: JSON.stringify(payload)
});
return await response.json();
};
// Example key: store this in your DB
const idempotencyKey = `order_${userId}_${Date.now()}`;
Handling 3D Secure 2.0 (EU Compliance)
3D Secure 2.0 is required for many EU payments:
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', // or 'mandatory'
notification_url: 'https://your-site.com/3ds-callback'
}
})
});
const result = await response.json();
// Redirect customer for authentication if needed
if (result.three_ds_redirect_url) {
res.redirect(result.three_ds_redirect_url);
}
return result;
};
Multi-Currency Pricing
Show localized prices 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;
};
// Usage
const eurPrice = await getLocalizedPrice(99.99, 'EUR');
console.log(`Price: EUR ${eurPrice.toFixed(2)}`);
Proration for Subscription Upgrades
Charge customers the prorated difference on plan upgrade:
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 await response.json();
};
Troubleshooting Common Issues
Webhooks Not Arriving
Symptoms: Orders process, but your system doesn't update.
Check:
// Review webhook delivery logs in the 2Checkout dashboard
// Look for failed deliveries or non-200 responses
Solutions:
- Ensure endpoint returns 200 OK within 5 seconds.
- Check SSL certificate validity (HTTPS required).
- Whitelist 2Checkout IPs in your firewall.
- Review signature verification logic.
- Test with webhook simulator before production.
Test Payments Fail in Sandbox
Symptoms: All test cards are declined in sandbox.
Solutions:
- Use sandbox API keys.
- Verify base URL:
https://sandbox.2checkout.com/api/ - Use correct test card numbers.
- Check sandbox account status (may expire after inactivity).
Subscription Renewals Silent Fail
Symptoms: Subscriptions active, but renewals not processed.
Check:
// Query subscription payment history
const history = await fetch(
`https://api.2checkout.com/1/subscriptions/${subId}/payments`,
{ headers: { 'X-Api-Key': privateKey } }
);
Solutions:
- Check payment method expiration.
- Review dunning settings.
- Verify webhook delivery for
subscription.payment_failed. - Confirm
auto_renewflag is enabled.
Currency Conversion Discrepancies
Symptoms: Charged amount differs from expected.
Cause: 2Checkout uses daily rates.
Solution:
- Display “approximate” conversion with disclaimer.
- Lock rates at cart creation for 15 minutes.
- Store transactions in customer’s local currency.
AVS (Address Verification) Failures
Symptoms: Legitimate cards decline due to address mismatch.
Solutions:
- Use address autocomplete (Google Places, Lob).
- Require ZIP/postal code at checkout.
- Implement soft AVS (warn instead of decline).
- Allow customer to update billing address.
Subscription Management
Use these endpoints to handle recurring billing.
Creating a Subscription
const createSubscription = async (customerId, planId) => {
const payload = {
customer_id: customerId,
plan_id: planId,
start_date: new Date().toISOString(),
billing_cycle: 'monthly', // or 'annual', 'weekly'
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 await response.json();
};
Subscription Response Example
{
"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"
}
Updating a Subscription
Update plan, payment method, or quantity:
const updateSubscription = async (subscriptionId, updates) => {
const payload = {
...updates
// Examples:
// 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 await response.json();
};
Canceling a Subscription
const cancelSubscription = async (subscriptionId, reason = '') => {
const payload = {
cancel_at_period_end: false, // true = cancel after current period, false = immediate
reason: 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 await response.json();
};
Webhook Integration: Real-Time Event Handling
Webhooks notify your system of payment events without polling. Essential for subscription renewals, failed payments, and refunds.
Step 1: Configure Webhook Endpoint
In your 2Checkout Control Panel:
- Go to Integrations > Webhooks
- Add your HTTPS endpoint URL
- Select events to subscribe to
- Save and note your Webhook Secret
Step 2: Create Webhook Handler
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;
// Verify webhook signature
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());
// Route to appropriate handler
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);
}
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 | Action Required |
|---|---|---|
order.created |
New order placed | Send confirmation email |
order.approved |
Payment successful | Fulfill order, grant access |
order.declined |
Payment failed | Notify customer, retry |
subscription.renewed |
Recurring payment | Extend access period |
subscription.payment_failed |
Renewal failed | Dunning sequence |
subscription.cancelled |
Customer canceled | Revoke access at end |
refund.processed |
Refund issued | Update user balance |
chargeback.received |
Dispute filed | Gather evidence |
Webhook Best Practices
- Always verify signatures – Prevent spoofing.
- Return 200 OK quickly – 2Checkout retries on non-200.
- Process asynchronously – Queue background jobs.
- Implement idempotency – Handle duplicate deliveries.
- Log all events – For debugging and audit trails.
Testing Your Integration
Using the Sandbox Environment
Test without real charges:
// Use sandbox base URL
const BASE_URL = 'https://sandbox.2checkout.com/api/1';
// Test card numbers
const TEST_CARDS = {
APPROVED: '4111111111111111',
DECLINED: '4000000000000002',
INSUFFICIENT_FUNDS: '4000000000009995',
EXPIRED_CARD: '4000000000000069'
};
// Test addresses
const TEST_ADDRESS = {
country: 'US',
zip: '90210' // Triggers AVS checks
};
Testing Webhooks Locally
Expose your local server using ngrok:
# Install ngrok
npm install -g ngrok
# Start your server on port 3000
node server.js
# Expose to internet
ngrok http 3000
# Copy the ngrok URL to 2Checkout webhook settings
Apidog for API Testing
Apidog streamlines 2Checkout API testing:
- Import OpenAPI Spec – Load 2Checkout’s API definition.
- Create Test Scenarios – Build collections for each endpoint.
- Mock Responses – Test workflows without calling the API.
- Validate Webhooks – Inspect payloads and signatures.
- Share with Team – Collaborate on integration testing.
Set up environment variables for sandbox and production keys, then switch contexts with one click.
Production Deployment Checklist
Before going live, verify:
- [ ] Switch from sandbox to production API keys
- [ ] Update base URL to
https://api.2checkout.com/ - [ ] Enable webhook signature verification
- [ ] Monitor failed payments
- [ ] Configure retry logic for failures
- [ ] Test refund and chargeback flows
- [ ] Ensure PCI DSS compliance (use tokenization)
- [ ] Enable 3D Secure 2.0 for EU customers
- [ ] Set up logging/audit trails
- [ ] Create runbook for payment issues
Monitoring and Alerting
Track key metrics and alert on anomalies:
// Example: Payment success rate
const successRate = approvedOrders / totalOrders * 100;
if (successRate < 95) {
sendAlert('Payment success rate dropped below 95%');
}
// Error tracking
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');
}
Real-World Use Cases
E-commerce Store Integration
A fashion retailer integrated 2Checkout for global payments:
- Supported 100+ currencies
- Reduced cart abandonment by 23%
- Automated EU VAT compliance
- Processed $2M+ in first year
Implementation: Started with hosted checkout, then migrated to direct API for custom UX.
SaaS Subscription Business
A SaaS company managed 5,000+ subscriptions via 2Checkout:
- Supported proration for upgrades
- Automated dunning for failed payments
- Reduced churn by 15% with smart retries
Key: Webhook-driven access control—extend access on subscription.renewed, revoke on subscription.cancelled.
Conclusion
The 2Checkout API provides the tools you need for robust payment processing and subscription management. To deploy successfully:
- Use the sandbox for all development and testing
- Implement HMAC signature verification on webhooks
- Handle errors with specific code logic
- Test subscription flows (trial, renewal, cancellation)
- Monitor payment metrics in production
- Use Apidog to speed up API testing and team collaboration
FAQ Section
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. 2Checkout was acquired by Verifone in 2020 and rebranded as Verifone Digital Commerce. API endpoints and features remain unchanged.
How do I get my 2Checkout API key?
Log into your 2Checkout Control Panel, navigate to Integrations > API Keys, and generate a new key. You get a private key (server-side) and public key (for tokenization).
Does 2Checkout have a sandbox environment?
Yes. Use https://sandbox.2checkout.com/api/ for testing. Create a separate sandbox account for test API keys and transactions.
What payment methods does 2Checkout support?
2Checkout supports credit cards (Visa, Mastercard, Amex, Discover), PayPal, Apple Pay, Google Pay, bank transfers, and local methods in 100+ countries.
How do I handle webhooks securely?
Always verify the X-Webhook-Signature header using HMAC-SHA256 with your webhook secret. Process events asynchronously and return 200 OK immediately.
What happens when a subscription payment fails?
2Checkout sends a subscription.payment_failed webhook. Implement retry logic (e.g., 3 attempts over 7 days). If all retries fail, a subscription.cancelled webhook is sent.
Is 2Checkout PCI DSS compliant?
Yes, 2Checkout is PCI DSS Level 1 certified. Use client-side tokenization to avoid handling raw card data.
Can I test subscriptions in sandbox?
Yes. Sandbox supports full subscription lifecycle testing: trials, renewals, upgrades, downgrades, and cancellations. Use test card 4111111111111111.
How do I handle refunds via API?
Send a POST to /refunds with the order ID and refund amount. 2Checkout processes partial or full refunds and sends a refund.processed webhook on completion.

Top comments (0)