Creating Payment Links
This guide covers everything you need to know about creating payment links with the Zelta Pay API, including required fields, validation rules, and best practices.
Basic Payment Link Creation
Section titled “Basic Payment Link Creation”To create a payment link, send a POST request to the /v1/payment-links endpoint with the required parameters in the request body.
Endpoint
Section titled “Endpoint”POST /v1/payment-links
Request Body Parameters
Section titled “Request Body Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
concept | string | Yes | Description of what’s being paid for |
amount | integer | Yes | Amount in cents (minimum 100, maximum 200000) |
customerName | string | Yes | Name of the customer |
isTest | boolean | Yes | Whether this is a test payment |
customerEmail | string | No | Customer’s email address (valid email format) |
redirectUrl | string | No | Custom redirect URL after payment |
metadata | object | No | Custom metadata (max 20 keys, 8192 bytes) |
Field Validation
Section titled “Field Validation”Amount
Section titled “Amount”- Minimum: 100 cents ($1.00)
- Maximum: 200,000 cents ($2,000.00)
- Type: Positive integer
Concept
Section titled “Concept”- Required: Cannot be empty
- Type: String
Customer Name
Section titled “Customer Name”- Required: Cannot be empty
- Type: String
Customer Email
Section titled “Customer Email”- Type: Valid email format
- Optional: Will prefill the email field on the hosted payment page
Redirect URL
Section titled “Redirect URL”- Type: Valid URL
- Maximum length: 2048 characters
- Protocol: HTTPS recommended
Metadata
Section titled “Metadata”- Keys: 1-40 characters, alphanumeric and underscores
- Values: String (max 255 chars), number, boolean, or null
- Maximum keys: 20 per payment link
- Total size: Maximum 8192 bytes when JSON serialized
- Cannot be empty object: If metadata is provided, it must contain at least one key-value pair
Request Examples
Section titled “Request Examples”Basic Payment Link
Section titled “Basic Payment Link”cURL Example
Section titled “cURL Example”curl -X POST "https://api-pay.zelta.dev/v1/payment-links" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "concept": "Consulting Service", "amount": 2500, "customerName": "John Doe", "isTest": true }'Node.js Example
Section titled “Node.js Example”const paymentLinkData = { concept: "Consulting Service", amount: 2500, // $25.00 in cents customerName: "John Doe", isTest: true};
const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', { method: 'POST', headers: { 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json' }, body: JSON.stringify(paymentLinkData)});
const result = await response.json();console.log(result);Cloudflare Workers Example
Section titled “Cloudflare Workers Example”export default { async fetch(request, env, ctx) { const paymentLinkData = { concept: "Consulting Service", amount: 2500, // $25.00 in cents customerName: "John Doe", isTest: true };
const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', { method: 'POST', headers: { 'X-API-Key': env.ZELTA_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify(paymentLinkData) });
const result = await response.json(); return new Response(JSON.stringify(result), { headers: { 'Content-Type': 'application/json' } }); }};Hono with Cloudflare Workers Example
Section titled “Hono with Cloudflare Workers Example”import { Hono } from 'hono';import { cors } from 'hono/cors';
type Bindings = { ZELTA_API_KEY: string;};
const app = new Hono<{ Bindings: Bindings }>();
// Enable CORSapp.use('*', cors());
app.post('/payment-links', async (c) => { const paymentLinkData = { concept: "Consulting Service", amount: 2500, // $25.00 in cents customerName: "John Doe", isTest: true };
const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', { method: 'POST', headers: { 'X-API-Key': c.env.ZELTA_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify(paymentLinkData) });
const result = await response.json(); return c.json(result);});
export default app;Python Example
Section titled “Python Example”import requestsimport json
payment_link_data = { "concept": "Consulting Service", "amount": 2500, # $25.00 in cents "customerName": "John Doe", "isTest": True}
headers = { 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json'}
response = requests.post( 'https://api-pay.zelta.dev/v1/payment-links', headers=headers, data=json.dumps(payment_link_data))
result = response.json()print(result)PHP Example
Section titled “PHP Example”$paymentLinkData = [ 'concept' => 'Consulting Service', 'amount' => 2500, // $25.00 in cents 'customerName' => 'John Doe', 'isTest' => true];
$headers = [ 'X-API-Key: your-api-key-here', 'Content-Type: application/json'];
$ch = curl_init();curl_setopt($ch, CURLOPT_URL, 'https://api-pay.zelta.dev/v1/payment-links');curl_setopt($ch, CURLOPT_POST, true);curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($paymentLinkData));curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);
$result = json_decode($response, true);Payment Link with Metadata
Section titled “Payment Link with Metadata”cURL Example
Section titled “cURL Example”curl -X POST "https://api-pay.zelta.dev/v1/payment-links" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "concept": "Order #12345 - Electronics Store", "amount": 15999, "customerName": "Alice Johnson", "isTest": false, "redirectUrl": "https://mystore.com/orders/12345/success", "metadata": { "orderId": "ORD-12345", "customerId": "CUST-789", "items": "laptop, mouse, keyboard", "shippingMethod": "express", "couponCode": "SAVE10" } }'Node.js Example
Section titled “Node.js Example”const paymentLinkData = { concept: "Order #12345 - Electronics Store", amount: 15999, // $159.99 in cents customerName: "Alice Johnson", isTest: false, redirectUrl: "https://mystore.com/orders/12345/success", metadata: { orderId: "ORD-12345", customerId: "CUST-789", items: "laptop, mouse, keyboard", shippingMethod: "express", couponCode: "SAVE10" }};
const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', { method: 'POST', headers: { 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json' }, body: JSON.stringify(paymentLinkData)});
const result = await response.json();console.log(result);Cloudflare Workers Example
Section titled “Cloudflare Workers Example”export default { async fetch(request, env, ctx) { const paymentLinkData = { concept: "Order #12345 - Electronics Store", amount: 15999, // $159.99 in cents customerName: "Alice Johnson", isTest: false, redirectUrl: "https://mystore.com/orders/12345/success", metadata: { orderId: "ORD-12345", customerId: "CUST-789", items: "laptop, mouse, keyboard", shippingMethod: "express", couponCode: "SAVE10" } };
const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', { method: 'POST', headers: { 'X-API-Key': env.ZELTA_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify(paymentLinkData) });
const result = await response.json(); return new Response(JSON.stringify(result), { headers: { 'Content-Type': 'application/json' } }); }};Hono with Cloudflare Workers Example
Section titled “Hono with Cloudflare Workers Example”import { Hono } from 'hono';import { cors } from 'hono/cors';
type Bindings = { ZELTA_API_KEY: string;};
const app = new Hono<{ Bindings: Bindings }>();
// Enable CORSapp.use('*', cors());
app.post('/payment-links', async (c) => { const paymentLinkData = { concept: "Order #12345 - Electronics Store", amount: 15999, // $159.99 in cents customerName: "Alice Johnson", isTest: false, redirectUrl: "https://mystore.com/orders/12345/success", metadata: { orderId: "ORD-12345", customerId: "CUST-789", items: "laptop, mouse, keyboard", shippingMethod: "express", couponCode: "SAVE10" } };
const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', { method: 'POST', headers: { 'X-API-Key': c.env.ZELTA_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify(paymentLinkData) });
const result = await response.json(); return c.json(result);});
export default app;Python Example
Section titled “Python Example”payment_link_data = { "concept": "Order #12345 - Electronics Store", "amount": 15999, # $159.99 in cents "customerName": "Alice Johnson", "isTest": False, "redirectUrl": "https://mystore.com/orders/12345/success", "metadata": { "orderId": "ORD-12345", "customerId": "CUST-789", "items": "laptop, mouse, keyboard", "shippingMethod": "express", "couponCode": "SAVE10" }}
headers = { 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json'}
response = requests.post( 'https://api-pay.zelta.dev/v1/payment-links', headers=headers, data=json.dumps(payment_link_data))
result = response.json()print(result)Response Format
Section titled “Response Format”Success Response
Section titled “Success Response”{ "success": true, "data": { "paymentLink": { "id": "pl_1234567890abcdef", "paymentLinkUrl": "https://pay.zelta.dev/abc123", "customerName": "John Doe", "concept": "Consulting Service", "amount": 2500, "status": "pending", "createdAt": "2024-01-15T10:30:00.000Z", "expiresAt": "2024-01-22T10:30:00.000Z", "cancelledAt": null, "completedAt": null, "isTest": true, "redirectUrl": "https://myapp.com/success", "metadata": { "orderId": "ORD-001", "service": "consulting" } } }, "message": "Payment link created successfully", "timestamp": "2024-01-15T10:30:00.000Z"}Response Fields
Section titled “Response Fields”| Field | Type | Description |
|---|---|---|
id | string | Unique payment link identifier |
paymentLinkUrl | string | URL for the payment link |
customerName | string | Name of the customer |
customerEmail | string|null | Customer’s email address |
concept | string | Description of what’s being paid for |
amount | integer | Amount in cents |
status | string | Payment link status (always “pending” for new links) |
createdAt | string | ISO 8601 timestamp of creation |
expiresAt | string | ISO 8601 timestamp of expiration (7 days from creation) |
cancelledAt | string|null | ISO 8601 timestamp of cancellation |
completedAt | string|null | ISO 8601 timestamp of completion |
isTest | boolean | Whether this is a test payment |
redirectUrl | string|null | Custom redirect URL |
metadata | object|null | Custom metadata |
Status Codes
Section titled “Status Codes”| Code | Description |
|---|---|
201 | Created - Payment link created successfully |
400 | Bad Request - Validation error or no active payment provider |
401 | Unauthorized - Missing or invalid API key |
403 | Forbidden - Account suspended |
404 | Not Found - Account not found |
429 | Too Many Requests - Rate limit exceeded |
Error Responses
Section titled “Error Responses”Validation Error
Section titled “Validation Error”{ "success": false, "message": "Validation error", "error": { "code": "ERR_VALIDATION_FAILED", "details": "Amount must be a positive integer" }, "timestamp": "2024-01-15T10:30:00.000Z"}No Active Payment Provider
Section titled “No Active Payment Provider”{ "success": false, "message": "No active payment provider found", "error": { "code": "ERR_NO_ACTIVE_PAYMENT_PROVIDER" }, "timestamp": "2024-01-15T10:30:00.000Z"}Missing Required Fields
Section titled “Missing Required Fields”{ "success": false, "message": "Validation error", "error": { "code": "ERR_VALIDATION_FAILED", "details": "Concept is required" }, "timestamp": "2024-01-15T10:30:00.000Z"}Invalid Amount
Section titled “Invalid Amount”{ "success": false, "message": "Validation error", "error": { "code": "ERR_VALIDATION_FAILED", "details": "Amount must be less than 2,000.00 USD (or 200,000 cents)" }, "timestamp": "2024-01-15T10:30:00.000Z"}Metadata Validation Error
Section titled “Metadata Validation Error”{ "success": false, "message": "Validation error", "error": { "code": "ERR_VALIDATION_FAILED", "details": "Metadata cannot have more than 20 keys" }, "timestamp": "2024-01-15T10:30:00.000Z"}Implementation Examples
Section titled “Implementation Examples”Basic Payment Link Creation
Section titled “Basic Payment Link Creation”Node.js Example
Section titled “Node.js Example”async function createPaymentLink(apiKey, data) { try { const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', { method: 'POST', headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
const result = await response.json();
if (!response.ok) { throw new Error(result.message); }
return result.data.paymentLink; } catch (error) { console.error('Error creating payment link:', error); throw error; }}
// Usageconst paymentLink = await createPaymentLink('your-api-key', { concept: 'Consulting Service', amount: 2500, customerName: 'John Doe', isTest: true});
console.log('Payment link created:', paymentLink);Cloudflare Workers Example
Section titled “Cloudflare Workers Example”export default { async fetch(request, env, ctx) { async function createPaymentLink(apiKey, data) { try { const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', { method: 'POST', headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
const result = await response.json();
if (!response.ok) { throw new Error(result.message); }
return result.data.paymentLink; } catch (error) { console.error('Error creating payment link:', error); throw error; } }
// Usage try { const paymentLink = await createPaymentLink(env.ZELTA_API_KEY, { concept: 'Consulting Service', amount: 2500, customerName: 'John Doe', isTest: true });
return new Response(JSON.stringify({ paymentLink }), { headers: { 'Content-Type': 'application/json' } }); } catch (error) { return new Response(JSON.stringify({ error: error.message }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } }};Hono with Cloudflare Workers Example
Section titled “Hono with Cloudflare Workers Example”import { Hono } from 'hono';import { cors } from 'hono/cors';
type Bindings = { ZELTA_API_KEY: string;};
const app = new Hono<{ Bindings: Bindings }>();
// Enable CORSapp.use('*', cors());
async function createPaymentLink(apiKey: string, data: any) { try { const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', { method: 'POST', headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
const result = await response.json();
if (!response.ok) { throw new Error(result.message); }
return result.data.paymentLink; } catch (error) { console.error('Error creating payment link:', error); throw error; }}
app.post('/create-payment-link', async (c) => { try { const paymentLink = await createPaymentLink(c.env.ZELTA_API_KEY, { concept: 'Consulting Service', amount: 2500, customerName: 'John Doe', isTest: true });
return c.json({ paymentLink }); } catch (error) { return c.json({ error: error.message }, 500); }});
export default app;Python Example
Section titled “Python Example”import requestsimport json
def create_payment_link(api_key, data): try: headers = { 'X-API-Key': api_key, 'Content-Type': 'application/json' }
response = requests.post( 'https://api-pay.zelta.dev/v1/payment-links', headers=headers, data=json.dumps(data) )
result = response.json()
if not response.ok: raise Exception(result['message'])
return result['data']['paymentLink'] except Exception as error: print(f'Error creating payment link: {error}') raise
# Usagepayment_link = create_payment_link('your-api-key', { 'concept': 'Consulting Service', 'amount': 2500, 'customerName': 'John Doe', 'isTest': True})
print('Payment link created:', payment_link)E-commerce Order Payment Link
Section titled “E-commerce Order Payment Link”Node.js Example
Section titled “Node.js Example”async function createOrderPaymentLink(apiKey, order) { const paymentLinkData = { concept: `Order #${order.id} - ${order.storeName}`, amount: order.totalAmount, // Already in cents customerName: order.customerName, isTest: process.env.NODE_ENV !== 'production', redirectUrl: `${process.env.FRONTEND_URL}/orders/${order.id}/success`, metadata: { orderId: order.id, customerId: order.customerId, items: order.items.map(item => item.name).join(', '), shippingMethod: order.shippingMethod, couponCode: order.couponCode || null } };
return await createPaymentLink(apiKey, paymentLinkData);}
// Usageconst order = { id: 'ORD-12345', storeName: 'Electronics Store', totalAmount: 15999, customerName: 'Alice Johnson', customerId: 'CUST-789', items: [ { name: 'laptop' }, { name: 'mouse' }, { name: 'keyboard' } ], shippingMethod: 'express', couponCode: 'SAVE10'};
const paymentLink = await createOrderPaymentLink('your-api-key', order);console.log('Order payment link:', paymentLink);Python Example
Section titled “Python Example”def create_order_payment_link(api_key, order): payment_link_data = { 'concept': f'Order #{order["id"]} - {order["store_name"]}', 'amount': order['total_amount'], # Already in cents 'customerName': order['customer_name'], 'isTest': os.getenv('FLASK_ENV') != 'production', 'redirectUrl': f"{os.getenv('FRONTEND_URL')}/orders/{order['id']}/success", 'metadata': { 'orderId': order['id'], 'customerId': order['customer_id'], 'items': ', '.join([item['name'] for item in order['items']]), 'shippingMethod': order['shipping_method'], 'couponCode': order.get('coupon_code') } }
return create_payment_link(api_key, payment_link_data)
# Usageorder = { 'id': 'ORD-12345', 'store_name': 'Electronics Store', 'total_amount': 15999, 'customer_name': 'Alice Johnson', 'customer_id': 'CUST-789', 'items': [ {'name': 'laptop'}, {'name': 'mouse'}, {'name': 'keyboard'} ], 'shipping_method': 'express', 'coupon_code': 'SAVE10'}
payment_link = create_order_payment_link('your-api-key', order)print('Order payment link:', payment_link)Subscription Payment Link
Section titled “Subscription Payment Link”Node.js Example
Section titled “Node.js Example”async function createSubscriptionPaymentLink(apiKey, subscription) { const paymentLinkData = { concept: `${subscription.planName} - ${subscription.billingCycle}`, amount: subscription.amount, customerName: subscription.customerName, isTest: process.env.NODE_ENV !== 'production', redirectUrl: `${process.env.FRONTEND_URL}/subscriptions/${subscription.id}/success`, metadata: { subscriptionId: subscription.id, customerId: subscription.customerId, plan: subscription.planName, billingCycle: subscription.billingCycle, startDate: subscription.startDate, trialEndsAt: subscription.trialEndsAt } };
return await createPaymentLink(apiKey, paymentLinkData);}
// Usageconst subscription = { id: 'SUB-456', planName: 'Premium Plan', billingCycle: 'monthly', amount: 2999, customerName: 'Bob Smith', customerId: 'CUST-123', startDate: '2024-01-15', trialEndsAt: '2024-02-15'};
const paymentLink = await createSubscriptionPaymentLink('your-api-key', subscription);console.log('Subscription payment link:', paymentLink);Validation Helpers
Section titled “Validation Helpers”Amount Validation
Section titled “Amount Validation”Node.js Example
Section titled “Node.js Example”function validateAmount(dollars) { const cents = Math.round(dollars * 100);
if (cents < 100) { throw new Error('Amount must be at least $1.00'); }
if (cents > 200000) { throw new Error('Amount cannot exceed $2,000.00'); }
return cents;}
// Usageconst amount = validateAmount(25.99); // Returns 2599Python Example
Section titled “Python Example”def validate_amount(dollars): cents = round(dollars * 100)
if cents < 100: raise ValueError('Amount must be at least $1.00')
if cents > 200000: raise ValueError('Amount cannot exceed $2,000.00')
return cents
# Usageamount = validate_amount(25.99) # Returns 2599Metadata Validation
Section titled “Metadata Validation”Node.js Example
Section titled “Node.js Example”function validateMetadata(metadata) { if (!metadata || Object.keys(metadata).length === 0) { throw new Error('Metadata cannot be empty'); }
if (Object.keys(metadata).length > 20) { throw new Error('Metadata cannot have more than 20 keys'); }
for (const [key, value] of Object.entries(metadata)) { if (key.length < 1 || key.length > 40) { throw new Error('Metadata key must be 1-40 characters'); }
if (typeof value === 'string' && value.length > 255) { throw new Error('Metadata string value cannot exceed 255 characters'); } }
// Check total size const jsonString = JSON.stringify(metadata); if (new TextEncoder().encode(jsonString).byteLength > 8192) { throw new Error('Metadata is too large'); }
return metadata;}
// Usageconst metadata = validateMetadata({ orderId: 'ORD-001', customerId: 12345, isVip: true});Python Example
Section titled “Python Example”def validate_metadata(metadata): if not metadata or len(metadata) == 0: raise ValueError('Metadata cannot be empty')
if len(metadata) > 20: raise ValueError('Metadata cannot have more than 20 keys')
for key, value in metadata.items(): if len(key) < 1 or len(key) > 40: raise ValueError('Metadata key must be 1-40 characters')
if isinstance(value, str) and len(value) > 255: raise ValueError('Metadata string value cannot exceed 255 characters')
# Check total size json_string = json.dumps(metadata) if len(json_string.encode('utf-8')) > 8192: raise ValueError('Metadata is too large')
return metadata
# Usagemetadata = validate_metadata({ 'orderId': 'ORD-001', 'customerId': 12345, 'isVip': True})Best Practices
Section titled “Best Practices”1. Error Handling
Section titled “1. Error Handling”Always handle validation errors gracefully:
async function createPaymentLinkWithValidation(apiKey, data) { try { // Validate amount if (data.amount < 100 || data.amount > 200000) { throw new Error('Amount must be between $1.00 and $2,000.00'); }
// Validate required fields if (!data.concept || !data.customerName) { throw new Error('Concept and customerName are required'); }
return await createPaymentLink(apiKey, data); } catch (error) { console.error('Validation error:', error.message); throw error; }}2. Use Test Mode
Section titled “2. Use Test Mode”Always use test mode during development:
const isTest = process.env.NODE_ENV !== 'production';
const paymentLinkData = { concept: 'Test Payment', amount: 100, customerName: 'Test User', isTest: isTest};3. Metadata Strategy
Section titled “3. Metadata Strategy”Use consistent metadata keys and appropriate data types:
const metadata = { orderId: 'ORD-001', // String customerId: 12345, // Number isVip: true, // Boolean notes: null // Null};4. Retry Logic
Section titled “4. Retry Logic”Implement retry logic for network errors:
async function createPaymentLinkWithRetry(apiKey, data, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await createPaymentLink(apiKey, data); } catch (error) { if (attempt === maxRetries - 1) { throw error; }
// Exponential backoff await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000)); } }}Testing
Section titled “Testing”Test Payment Links
Section titled “Test Payment Links”Use test mode for development and testing:
{ "concept": "Test Payment", "amount": 100, "customerName": "Test User", "isTest": true}Validation Testing
Section titled “Validation Testing”Test validation rules:
// Test minimum amountconst minAmountTest = { concept: 'Test', amount: 50, // Below minimum customerName: 'Test', isTest: true};
// Test maximum amountconst maxAmountTest = { concept: 'Test', amount: 300000, // Above maximum customerName: 'Test', isTest: true};Next Steps
Section titled “Next Steps”Now that you understand payment link creation, explore these guides:
- Webhooks - Receive real-time payment notifications
- Signature Verification - Secure webhook handling
- Use Cases - Real-world integration examples
- API Reference - Complete endpoint documentation