Create Payment Link
Create a new payment link for your customers to pay.
Endpoint
Section titled “Endpoint”POST /v1/payment-linksAuthentication
Section titled “Authentication”Requires API key authentication via X-API-Key header.
Request Body
Section titled “Request Body”| Field | 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 |
customerEmail | string | No | Customer’s email address (valid email format) |
isTest | boolean | Yes | Whether this is a test payment |
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);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