Skip to content

Quick Start

Welcome to Zelta Pay API! This guide will help you create your first payment link and understand the basics of our API.

Before you begin, make sure you have:

  • A Zelta Pay account
  • An API key (obtain from your dashboard)
  • Basic knowledge of HTTP requests
  • A development environment ready
  1. Log in to your Zelta Pay Dashboard
  2. Open API Key in the sidebar (shown to the admin and owner roles only)
  3. In the Nueva API key card, type a descriptive alias (e.g., “My App Integration”) and click Crear. The button stays disabled until the account has an active payment provider
  4. Click Ver llave on the new row, then Copiar, and store the key securely

Let’s test your API key by listing your existing payment links:

Terminal window
curl -X GET "https://api-pay.zelta.dev/v1/payment-links" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json"
const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', {
method: 'GET',
headers: {
'X-API-Key': 'your-api-key-here',
'Content-Type': 'application/json'
}
});
const result = await response.json();
console.log(result);
export default {
async fetch(request, env, ctx) {
const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', {
method: 'GET',
headers: {
'X-API-Key': env.ZELTA_API_KEY,
'Content-Type': 'application/json'
}
});
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.get('/payment-links', async (c) => {
const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', {
method: 'GET',
headers: {
'X-API-Key': c.env.ZELTA_API_KEY,
'Content-Type': 'application/json'
}
});
const result = await response.json();
return c.json(result);
});
export default app;
import requests
response = requests.get(
'https://api-pay.zelta.dev/v1/payment-links',
headers={
'X-API-Key': 'your-api-key-here',
'Content-Type': 'application/json'
}
)
result = response.json()
print(result)

Now let’s create a payment link:

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": "Test Payment",
"amount": 100,
"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({
concept: 'Test Payment',
amount: 100,
customerName: 'John Doe',
isTest: true
})
});
const result = await response.json();
console.log(result);
export default {
async fetch(request, env, ctx) {
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({
concept: 'Test Payment',
amount: 100,
customerName: 'John Doe',
isTest: true
})
});
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 body = await c.req.json();
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({
concept: 'Test Payment',
amount: 100,
customerName: 'John Doe',
isTest: true
})
});
const result = await response.json();
return c.json(result);
});
export default app;
import requests
response = requests.post(
'https://api-pay.zelta.dev/v1/payment-links',
headers={
'X-API-Key': 'your-api-key-here',
'Content-Type': 'application/json'
},
json={
'concept': 'Test Payment',
'amount': 100,
'customerName': 'John Doe',
'isTest': True
}
)
result = response.json()
print(result)

A successful response will look like this:

{
"success": true,
"data": {
"paymentLink": {
"id": "7312345678901234567",
"paymentLinkUrl": "https://pay.zelta.dev/Xk3v9Q_aB7mN2pL8rT4wYz1c",
"customerName": "John Doe",
"concept": "Test Payment",
"amount": 100,
"status": "pending",
"createdAt": "2024-01-15T10:30:00.000Z",
"expiresAt": "2024-01-18T10:30:00.000Z",
"isTest": true
}
},
"message": "Payment link created successfully",
"timestamp": "2024-01-15T10:30:00.000Z"
}
  • paymentLinkUrl: The URL your customer will use to pay. Its last path segment is the link’s hashUrl, which you pass to GET /v1/payment-links/{hashUrl} and to the cancel endpoint
  • id: Unique identifier for the payment link (a numeric string)
  • status: Current status (always “pending” for new links)
  • expiresAt: When the link expires: 3 days after creation by default. Send expiresInDays (1 to 60) to choose another lifetime; see Create Payment Link

Once you have the payment link URL, you can:

  1. Send it to your customer via email, SMS, or display it on your website
  2. Redirect them to the payment portal
  3. Monitor the status by calling the API or setting up webhooks
// Redirect customer to payment page
window.location.href = result.data.paymentLink.paymentLinkUrl;

To receive real-time notifications when payments are completed:

  1. Open Webhooks in the dashboard sidebar (shown to the admin and owner roles only)
  2. In the Nuevo webhook card, enter your webhook URL (must be a public HTTPS URL) and click Crear. An account has a single webhook, and creating it requires an active payment provider
  3. Click Enviar ping to send a signed webhook.ping to your endpoint

There is no event selection: the webhook receives every event for the account’s payment links (payment.success, payment.failed, receipt.created…). Ignore the types you don’t handle.

// Node.js with Express
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['zeltapay-signature'];
const timestamp = req.headers['zeltapay-timestamp'];
// Verify signature (important for security)
if (!verifySignature(req.body.toString(), timestamp, signature)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const payload = JSON.parse(req.body);
if (payload.type === 'payment.success') {
console.log('Payment completed:', payload.transaction.amount);
// Update your database, send confirmation email, etc.
}
res.status(200).json({ received: true });
});
export default {
async fetch(request, env, ctx) {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const url = new URL(request.url);
if (url.pathname !== '/webhook') {
return new Response('Not found', { status: 404 });
}
try {
const signature = request.headers.get('zeltapay-signature');
const timestamp = request.headers.get('zeltapay-timestamp');
// Get raw body
const rawBody = await request.text();
// Verify signature (important for security)
if (!await verifySignature(rawBody, timestamp, signature, env.WEBHOOK_SECRET)) {
return new Response(JSON.stringify({ error: 'Invalid signature' }), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
const payload = JSON.parse(rawBody);
if (payload.type === 'payment.success') {
console.log('Payment completed:', payload.transaction.amount);
// Update your database, send confirmation email, etc.
}
return new Response(JSON.stringify({ received: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
console.error('Webhook processing error:', error);
return new Response(JSON.stringify({ error: 'Processing failed' }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}
};
async function verifySignature(rawBody, timestamp, signature, secret) {
// Implementation details in signature verification guide
return true; // Placeholder
}

Hono with Cloudflare Workers Webhook Example

Section titled “Hono with Cloudflare Workers Webhook Example”
import { Hono } from 'hono';
import { cors } from 'hono/cors';
type Bindings = {
WEBHOOK_SECRET: string;
};
const app = new Hono<{ Bindings: Bindings }>();
// Enable CORS
app.use('*', cors());
app.post('/webhook', async (c) => {
try {
const signature = c.req.header('zeltapay-signature');
const timestamp = c.req.header('zeltapay-timestamp');
if (!signature || !timestamp) {
return c.json({ error: 'Missing required headers' }, 400);
}
// Get raw body
const rawBody = await c.req.text();
// Verify signature (important for security)
if (!await verifySignature(rawBody, timestamp, signature, c.env.WEBHOOK_SECRET)) {
return c.json({ error: 'Invalid signature' }, 401);
}
const payload = JSON.parse(rawBody);
if (payload.type === 'payment.success') {
console.log('Payment completed:', payload.transaction.amount);
// Update your database, send confirmation email, etc.
}
return c.json({ received: true });
} catch (error) {
console.error('Webhook processing error:', error);
return c.json({ error: 'Processing failed' }, 500);
}
});
async function verifySignature(rawBody: string, timestamp: string, signature: string, secret: string): Promise<boolean> {
// Implementation details in signature verification guide
return true; // Placeholder
}
export default app;

Now that you’ve created your first payment link, explore these guides:

  • The X-API-Key header is missing: ensure it is included

404 Not Found (ERR_ACCOUNT_API_KEY_NOT_FOUND)

Section titled “404 Not Found (ERR_ACCOUNT_API_KEY_NOT_FOUND)”
  • The API key is wrong, was regenerated or was deleted. Keys have no active/inactive state
  • Copy the current key from API Key → Ver llave in the dashboard
  • Check that all required fields are provided (and sent with Content-Type: application/json)
  • Ensure the amount is in cents (e.g., 100 = $1.00); the minimum is 100
  • Verify the JSON format is correct
  • You’ve exceeded the rate limit (60 requests per 60-second window per API key)
  • Wait the number of seconds in the Retry-After header before making more requests (the body is plain text, not JSON)
  • Consider implementing request queuing

If you need help:

  • Documentation: Browse our guides and API reference
  • Support: Contact us at support@zelta.dev