Skip to content

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.

To create a payment link, send a POST request to the /v1/payment-links endpoint with the required parameters in the request body.

POST /v1/payment-links

ParameterTypeRequiredDescription
conceptstringYesDescription of what’s being paid for
amountintegerYesAmount in cents (minimum 100, maximum 200000)
customerNamestringYesName of the customer
isTestbooleanYesWhether this is a test payment
customerEmailstringNoCustomer’s email address (valid email format)
redirectUrlstringNoCustom redirect URL after payment
metadataobjectNoCustom metadata (max 20 keys, 8192 bytes)
  • Minimum: 100 cents ($1.00)
  • Maximum: 200,000 cents ($2,000.00)
  • Type: Positive integer
  • Required: Cannot be empty
  • Type: String
  • Required: Cannot be empty
  • Type: String
  • Type: Valid email format
  • Optional: Will prefill the email field on the hosted payment page
  • Type: Valid URL
  • Maximum length: 2048 characters
  • Protocol: HTTPS recommended
  • 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
Terminal window
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
}'
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);
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' }
});
}
};
import { Hono } from 'hono';
import { cors } from 'hono/cors';
type Bindings = {
ZELTA_API_KEY: string;
};
const app = new Hono<{ Bindings: Bindings }>();
// Enable CORS
app.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;
import requests
import 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)
$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);
Terminal window
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"
}
}'
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);
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' }
});
}
};
import { Hono } from 'hono';
import { cors } from 'hono/cors';
type Bindings = {
ZELTA_API_KEY: string;
};
const app = new Hono<{ Bindings: Bindings }>();
// Enable CORS
app.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;
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)
{
"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"
}
FieldTypeDescription
idstringUnique payment link identifier
paymentLinkUrlstringURL for the payment link
customerNamestringName of the customer
customerEmailstring|nullCustomer’s email address
conceptstringDescription of what’s being paid for
amountintegerAmount in cents
statusstringPayment link status (always “pending” for new links)
createdAtstringISO 8601 timestamp of creation
expiresAtstringISO 8601 timestamp of expiration (7 days from creation)
cancelledAtstring|nullISO 8601 timestamp of cancellation
completedAtstring|nullISO 8601 timestamp of completion
isTestbooleanWhether this is a test payment
redirectUrlstring|nullCustom redirect URL
metadataobject|nullCustom metadata
CodeDescription
201Created - Payment link created successfully
400Bad Request - Validation error or no active payment provider
401Unauthorized - Missing or invalid API key
403Forbidden - Account suspended
404Not Found - Account not found
429Too Many Requests - Rate limit exceeded
{
"success": false,
"message": "Validation error",
"error": {
"code": "ERR_VALIDATION_FAILED",
"details": "Amount must be a positive integer"
},
"timestamp": "2024-01-15T10:30:00.000Z"
}
{
"success": false,
"message": "No active payment provider found",
"error": {
"code": "ERR_NO_ACTIVE_PAYMENT_PROVIDER"
},
"timestamp": "2024-01-15T10:30:00.000Z"
}
{
"success": false,
"message": "Validation error",
"error": {
"code": "ERR_VALIDATION_FAILED",
"details": "Concept is required"
},
"timestamp": "2024-01-15T10:30:00.000Z"
}
{
"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"
}
{
"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"
}
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
const paymentLink = await createPaymentLink('your-api-key', {
concept: 'Consulting Service',
amount: 2500,
customerName: 'John Doe',
isTest: true
});
console.log('Payment link created:', paymentLink);
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' }
});
}
}
};
import { Hono } from 'hono';
import { cors } from 'hono/cors';
type Bindings = {
ZELTA_API_KEY: string;
};
const app = new Hono<{ Bindings: Bindings }>();
// Enable CORS
app.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;
import requests
import 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
# Usage
payment_link = create_payment_link('your-api-key', {
'concept': 'Consulting Service',
'amount': 2500,
'customerName': 'John Doe',
'isTest': True
})
print('Payment link created:', payment_link)
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);
}
// Usage
const 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);
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)
# Usage
order = {
'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)
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);
}
// Usage
const 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);
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;
}
// Usage
const amount = validateAmount(25.99); // Returns 2599
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
# Usage
amount = validate_amount(25.99) # Returns 2599
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;
}
// Usage
const metadata = validateMetadata({
orderId: 'ORD-001',
customerId: 12345,
isVip: true
});
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
# Usage
metadata = validate_metadata({
'orderId': 'ORD-001',
'customerId': 12345,
'isVip': True
})

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;
}
}

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
};

Use consistent metadata keys and appropriate data types:

const metadata = {
orderId: 'ORD-001', // String
customerId: 12345, // Number
isVip: true, // Boolean
notes: null // Null
};

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));
}
}
}

Use test mode for development and testing:

{
"concept": "Test Payment",
"amount": 100,
"customerName": "Test User",
"isTest": true
}

Test validation rules:

// Test minimum amount
const minAmountTest = {
concept: 'Test',
amount: 50, // Below minimum
customerName: 'Test',
isTest: true
};
// Test maximum amount
const maxAmountTest = {
concept: 'Test',
amount: 300000, // Above maximum
customerName: 'Test',
isTest: true
};

Now that you understand payment link creation, explore these guides: