Get Payment Link
Retrieve a single payment link by its hash URL.
Endpoint
Section titled “Endpoint”GET /v1/payment-links/{hashUrl}Authentication
Section titled “Authentication”Requires API key authentication via X-API-Key header.
An account-level key can read any payment link of the account. A project API key can only read the links of its project (the links created with the project’s key); any other link returns the same 404 ERR_PAYMENT_LINK_NOT_FOUND as a link that does not exist.
Path Parameters
Section titled “Path Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
hashUrl | string | Yes | The link’s 24-character hash: the last path segment of paymentLinkUrl (for https://pay.zelta.dev/Xk3v9Q_aB7mN2pL8rT4wYz1c it is Xk3v9Q_aB7mN2pL8rT4wYz1c). The link’s id cannot be used here |
Request Examples
Section titled “Request Examples”cURL Example
Section titled “cURL Example”curl -X GET "https://api-pay.zelta.dev/v1/payment-links/Xk3v9Q_aB7mN2pL8rT4wYz1c" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json"Node.js Example
Section titled “Node.js Example”const hashUrl = 'Xk3v9Q_aB7mN2pL8rT4wYz1c';const response = await fetch(`https://api-pay.zelta.dev/v1/payment-links/${hashUrl}`, { method: 'GET', headers: { 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json' }});
const result = await response.json();console.log(result);Python Example
Section titled “Python Example”import requests
hash_url = 'Xk3v9Q_aB7mN2pL8rT4wYz1c'headers = { 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json'}
response = requests.get( f'https://api-pay.zelta.dev/v1/payment-links/{hash_url}', headers=headers)
result = response.json()print(result)PHP Example
Section titled “PHP Example”$hashUrl = 'Xk3v9Q_aB7mN2pL8rT4wYz1c';$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/{$hashUrl}");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);Response Format
Section titled “Response Format”Success Response
Section titled “Success Response”{ "success": true, "data": { "paymentLink": { "id": "7312345678901234567", "paymentLinkUrl": "https://pay.zelta.dev/Xk3v9Q_aB7mN2pL8rT4wYz1c", "customerName": "John Doe", "concept": "Consulting Service", "amount": 2500, "status": "pending", "createdAt": "2024-01-15T10:30:00.000Z", "expiresAt": "2024-01-22T10:30:00.000Z", "isTest": true, "redirectUrl": "https://myapp.com/success", "metadata": { "orderId": "ORD-001", "service": "consulting" }, "paymentMethods": ["card", "yappy"], "requestCustomerEmail": true } }, "message": "Operation completed successfully", "timestamp": "2024-01-15T10:30:00.000Z"}Response Fields
Section titled “Response Fields”| Field | Type | Description |
|---|---|---|
id | string | Unique payment link identifier (a numeric string) |
paymentLinkUrl | string | URL for the payment link |
customerName | string | Name of the customer |
customerEmail | string (optional) | Customer’s email address. Omitted when not set |
concept | string | Description of what’s being paid for |
amount | integer | Amount in cents |
status | string | Stored payment link status: pending, processing, completed, cancelled or expired (see Payment Link Statuses) |
createdAt | string | ISO 8601 timestamp of creation |
expiresAt | string | ISO 8601 timestamp of expiration |
cancelledAt | string (optional) | ISO 8601 timestamp of cancellation. Omitted when not set |
completedAt | string (optional) | ISO 8601 timestamp of completion. Omitted when not set |
isTest | boolean | Whether this is a test payment |
redirectUrl | string (optional) | Custom redirect URL. Omitted when not set |
metadata | object (optional) | Custom metadata. Omitted when not set |
paymentMethods | string[] (optional) | Payment methods allowed when the link was created: any of "card", "yappy", "paypal". Omitted when none were specified (every active method is offered). |
requestCustomerEmail | boolean | Whether the payment page requests the customer’s email |
Payment Link Statuses
Section titled “Payment Link Statuses”| Status | Meaning |
|---|---|
pending | Waiting for payment |
processing | A payment is being confirmed. With PayPal this can last from minutes to several days while PayPal reviews the payment. The link cannot be paid again or cancelled meanwhile. |
completed | Payment successful |
cancelled | Manually cancelled |
expired | Past its expiresAt and stored as expired |
This endpoint returns the stored status and never updates it. Links are not expired proactively and no webhook is sent on expiry: a pending link past its expiresAt is stored as expired only when a listing (GET /v1/payment-links or the dashboard) returns it on a page, so until then this endpoint keeps returning pending. Compare expiresAt with the current time to know whether the link can still be paid.
A processing link normally ends as completed (you receive the payment.success webhook), or goes back to pending if the payment is declined or abandoned, so the customer can try again.
Status Codes
Section titled “Status Codes”| Code | Description |
|---|---|
200 | OK - Payment link retrieved successfully |
401 | Unauthorized - Missing X-API-Key header |
403 | Forbidden - Account suspended |
404 | Not Found - Unknown API key (ERR_ACCOUNT_API_KEY_NOT_FOUND), or payment link not found or not reachable with this key (ERR_PAYMENT_LINK_NOT_FOUND): another account’s link or, with a project key, a link outside the project |
429 | Too Many Requests - Rate limit exceeded (plain-text body; wait Retry-After seconds) |
Error Responses
Section titled “Error Responses”Payment Link Not Found
Section titled “Payment Link Not Found”{ "success": false, "message": "Payment link not found", "error": { "code": "ERR_PAYMENT_LINK_NOT_FOUND" }, "timestamp": "2024-01-15T10:30:00.000Z"}Missing API Key
Section titled “Missing API Key”{ "success": false, "message": "Missing X-API-Key header", "error": { "code": "ERR_API_KEY_MISSING" }, "timestamp": "2024-01-15T10:30:00.000Z"}Invalid API Key
Section titled “Invalid API Key”An unknown, regenerated or deleted key returns 404:
{ "success": false, "message": "Api key not found", "error": { "code": "ERR_ACCOUNT_API_KEY_NOT_FOUND" }, "timestamp": "2024-01-15T10:30:00.000Z"}Implementation Examples
Section titled “Implementation Examples”Basic Payment Link Retrieval
Section titled “Basic Payment Link Retrieval”Node.js Example
Section titled “Node.js Example”async function getPaymentLink(apiKey, hashUrl) { try { const response = await fetch(`https://api-pay.zelta.dev/v1/payment-links/${hashUrl}`, { method: 'GET', headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' } });
if (response.status === 429) { // Rate limited: plain-text body, wait Retry-After seconds throw new Error(`Rate limited, retry in ${response.headers.get('Retry-After')}s`); }
const result = await response.json();
if (!response.ok) { // 400 validation errors carry error.issues and no message const error = new Error(result.message ?? JSON.stringify(result.error?.issues)); error.code = result.error?.code; throw error; }
return result.data.paymentLink; } catch (error) { console.error('Error retrieving payment link:', error); throw error; }}
// Usageconst paymentLink = await getPaymentLink('your-api-key', 'Xk3v9Q_aB7mN2pL8rT4wYz1c');console.log('Payment link:', paymentLink);Python Example
Section titled “Python Example”import requests
def get_payment_link(api_key, hash_url): try: headers = { 'X-API-Key': api_key, 'Content-Type': 'application/json' }
response = requests.get( f'https://api-pay.zelta.dev/v1/payment-links/{hash_url}', headers=headers )
if response.status_code == 429: # Rate limited: plain-text body, wait Retry-After seconds raise Exception(f"Rate limited, retry in {response.headers.get('Retry-After')}s")
result = response.json()
if not response.ok: # 400 validation errors carry error.issues and no message raise Exception(result.get('message') or result['error'].get('issues'))
return result['data']['paymentLink'] except Exception as error: print(f'Error retrieving payment link: {error}') raise
# Usagepayment_link = get_payment_link('your-api-key', 'Xk3v9Q_aB7mN2pL8rT4wYz1c')print('Payment link:', payment_link)Payment Link Status Check
Section titled “Payment Link Status Check”Node.js Example
Section titled “Node.js Example”async function checkPaymentLinkStatus(apiKey, hashUrl) { try { const paymentLink = await getPaymentLink(apiKey, hashUrl); // The stored status can still read 'pending' after expiresAt const pastExpiry = new Date(paymentLink.expiresAt) <= new Date();
return { status: paymentLink.status, isCompleted: paymentLink.status === 'completed', isPending: paymentLink.status === 'pending' && !pastExpiry, isCancelled: paymentLink.status === 'cancelled', isExpired: paymentLink.status === 'expired' || (paymentLink.status === 'pending' && pastExpiry), completedAt: paymentLink.completedAt, cancelledAt: paymentLink.cancelledAt, expiresAt: paymentLink.expiresAt }; } catch (error) { console.error('Error checking payment link status:', error); throw error; }}
// Usageconst status = await checkPaymentLinkStatus('your-api-key', 'Xk3v9Q_aB7mN2pL8rT4wYz1c');console.log('Payment link status:', status);Python Example
Section titled “Python Example”from datetime import datetime, timezone
def check_payment_link_status(api_key, hash_url): try: payment_link = get_payment_link(api_key, hash_url) # The stored status can still read 'pending' after expiresAt expires_at = datetime.fromisoformat(payment_link['expiresAt'].replace('Z', '+00:00')) past_expiry = expires_at <= datetime.now(timezone.utc) status = payment_link['status']
return { 'status': status, 'is_completed': status == 'completed', 'is_pending': status == 'pending' and not past_expiry, 'is_cancelled': status == 'cancelled', 'is_expired': status == 'expired' or (status == 'pending' and past_expiry), # Optional fields are omitted when not set 'completed_at': payment_link.get('completedAt'), 'cancelled_at': payment_link.get('cancelledAt'), 'expires_at': payment_link['expiresAt'] } except Exception as error: print(f'Error checking payment link status: {error}') raise
# Usagestatus = check_payment_link_status('your-api-key', 'Xk3v9Q_aB7mN2pL8rT4wYz1c')print('Payment link status:', status)Order Status Check
Section titled “Order Status Check”Node.js Example
Section titled “Node.js Example”async function checkOrderStatus(apiKey, orderId) { try { // Get all payment links (every page, see List Payment Links) const allLinks = await getAllPaymentLinksPaginated(apiKey);
// Find payment link for this order const orderPaymentLink = allLinks.find(link => link.metadata?.orderId === orderId );
if (!orderPaymentLink) { throw new Error('Payment link not found for order'); }
// Get detailed payment link info const hashUrl = orderPaymentLink.paymentLinkUrl.split('/').pop(); const paymentLink = await getPaymentLink(apiKey, hashUrl);
return { orderId: orderId, paymentLinkId: paymentLink.id, paymentLinkUrl: paymentLink.paymentLinkUrl, status: paymentLink.status, amount: paymentLink.amount, customerName: paymentLink.customerName, concept: paymentLink.concept, createdAt: paymentLink.createdAt, completedAt: paymentLink.completedAt, cancelledAt: paymentLink.cancelledAt, expiresAt: paymentLink.expiresAt }; } catch (error) { console.error('Error checking order status:', error); throw error; }}
// Usageconst orderStatus = await checkOrderStatus('your-api-key', 'ORD-001');console.log('Order status:', orderStatus);Python Example
Section titled “Python Example”def check_order_status(api_key, order_id): try: # Get all payment links (every page, see List Payment Links) all_links = get_all_payment_links_paginated(api_key)
# Find payment link for this order order_payment_link = None for link in all_links: if link.get('metadata', {}).get('orderId') == order_id: order_payment_link = link break
if not order_payment_link: raise Exception('Payment link not found for order')
# Get detailed payment link info hash_url = order_payment_link['paymentLinkUrl'].split('/')[-1] payment_link = get_payment_link(api_key, hash_url)
return { 'orderId': order_id, 'paymentLinkId': payment_link['id'], 'paymentLinkUrl': payment_link['paymentLinkUrl'], 'status': payment_link['status'], 'amount': payment_link['amount'], 'customerName': payment_link['customerName'], 'concept': payment_link['concept'], 'createdAt': payment_link['createdAt'], # Optional fields are omitted when not set 'completedAt': payment_link.get('completedAt'), 'cancelledAt': payment_link.get('cancelledAt'), 'expiresAt': payment_link['expiresAt'] } except Exception as error: print(f'Error checking order status: {error}') raise
# Usageorder_status = check_order_status('your-api-key', 'ORD-001')print('Order status:', order_status)Use Cases
Section titled “Use Cases”1. Order Status Page
Section titled “1. Order Status Page”Check payment status for order confirmation pages:
async function getOrderPaymentStatus(apiKey, orderId) { try { const orderStatus = await checkOrderStatus(apiKey, orderId); // The stored status can still read 'pending' after expiresAt const pastExpiry = new Date(orderStatus.expiresAt) <= new Date();
return { orderId: orderStatus.orderId, paymentStatus: orderStatus.status, amount: orderStatus.amount, customerName: orderStatus.customerName, concept: orderStatus.concept, paymentUrl: orderStatus.paymentLinkUrl, // use the URL as returned isPaid: orderStatus.status === 'completed', isPending: orderStatus.status === 'pending' && !pastExpiry, isCancelled: orderStatus.status === 'cancelled', isExpired: orderStatus.status === 'expired' || (orderStatus.status === 'pending' && pastExpiry), completedAt: orderStatus.completedAt, expiresAt: orderStatus.expiresAt }; } catch (error) { console.error('Error getting order payment status:', error); throw error; }}
// Usage in order status pageconst paymentStatus = await getOrderPaymentStatus('your-api-key', 'ORD-001');console.log('Payment status:', paymentStatus);2. Payment Verification
Section titled “2. Payment Verification”Verify payment completion before processing orders:
async function verifyPaymentCompletion(apiKey, orderId) { try { const orderStatus = await checkOrderStatus(apiKey, orderId);
if (orderStatus.status === 'completed') { // Payment is complete, process the order await processOrder(orderId); return { success: true, message: 'Payment verified and order processed', orderId: orderId, amount: orderStatus.amount, completedAt: orderStatus.completedAt }; } else if (orderStatus.status === 'pending' && new Date(orderStatus.expiresAt) > new Date()) { // Payment is still pending (and the link has not expired) return { success: false, message: 'Payment is still pending', orderId: orderId, status: orderStatus.status, expiresAt: orderStatus.expiresAt }; } else { // Payment failed, cancelled, or expired return { success: false, message: 'Payment not completed', orderId: orderId, status: orderStatus.status, cancelledAt: orderStatus.cancelledAt, expiresAt: orderStatus.expiresAt }; } } catch (error) { console.error('Error verifying payment completion:', error); throw error; }}
// Usageconst verification = await verifyPaymentCompletion('your-api-key', 'ORD-001');console.log('Payment verification:', verification);3. Customer Service
Section titled “3. Customer Service”Retrieve payment information for customer support:
async function getCustomerPaymentInfo(apiKey, customerId) { try { // Get all payment links (every page, see List Payment Links) const allLinks = await getAllPaymentLinksPaginated(apiKey);
// Filter by customer ID const customerPayments = allLinks.filter(link => link.metadata?.customerId === customerId );
// Get detailed information for each payment const detailedPayments = await Promise.all( customerPayments.map(async (link) => { const hashUrl = link.paymentLinkUrl.split('/').pop(); const paymentLink = await getPaymentLink(apiKey, hashUrl);
return { paymentLinkId: paymentLink.id, status: paymentLink.status, amount: paymentLink.amount, concept: paymentLink.concept, createdAt: paymentLink.createdAt, completedAt: paymentLink.completedAt, cancelledAt: paymentLink.cancelledAt, expiresAt: paymentLink.expiresAt, metadata: paymentLink.metadata }; }) );
return { customerId: customerId, totalPayments: detailedPayments.length, completedPayments: detailedPayments.filter(p => p.status === 'completed').length, pendingPayments: detailedPayments.filter(p => p.status === 'pending' && new Date(p.expiresAt) > new Date() ).length, cancelledPayments: detailedPayments.filter(p => p.status === 'cancelled').length, // A link past expiresAt can still read 'pending' expiredPayments: detailedPayments.filter(p => p.status === 'expired' || (p.status === 'pending' && new Date(p.expiresAt) <= new Date()) ).length, totalAmount: detailedPayments .filter(p => p.status === 'completed') .reduce((sum, p) => sum + p.amount, 0), payments: detailedPayments }; } catch (error) { console.error('Error getting customer payment info:', error); throw error; }}
// Usageconst customerInfo = await getCustomerPaymentInfo('your-api-key', 'CUST-123');console.log('Customer payment info:', customerInfo);Best Practices
Section titled “Best Practices”1. Error Handling
Section titled “1. Error Handling”Always handle different error scenarios:
async function getPaymentLinkSafely(apiKey, hashUrl) { try { return await getPaymentLink(apiKey, hashUrl); } catch (error) { if (error.code === 'ERR_PAYMENT_LINK_NOT_FOUND') { return null; // Payment link doesn't exist (or belongs to another account) }
if (error.code === 'ERR_ACCOUNT_API_KEY_NOT_FOUND' || error.code === 'ERR_API_KEY_MISSING') { throw new Error('Authentication failed'); }
throw error; // Re-throw other errors }}2. Caching
Section titled “2. Caching”Cache payment link data to reduce API calls:
const paymentLinkCache = new Map();
async function getPaymentLinkWithCache(apiKey, hashUrl, ttl = 300000) { // 5 minutes const cacheKey = `${apiKey}:${hashUrl}`; const cached = paymentLinkCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < ttl) { return cached.data; }
const paymentLink = await getPaymentLink(apiKey, hashUrl);
paymentLinkCache.set(cacheKey, { data: paymentLink, timestamp: Date.now() });
return paymentLink;}3. Rate Limiting
Section titled “3. Rate Limiting”Implement rate limiting for batch operations:
async function getMultiplePaymentLinks(apiKey, hashUrls, delay = 1000) { const results = [];
for (const hashUrl of hashUrls) { try { const paymentLink = await getPaymentLink(apiKey, hashUrl); results.push({ hashUrl, success: true, paymentLink }); } catch (error) { results.push({ hashUrl, success: false, error: error.message }); }
// Rate limiting delay await new Promise(resolve => setTimeout(resolve, delay)); }
return results;}4. Logging
Section titled “4. Logging”Log payment link retrieval for audit purposes:
async function getPaymentLinkWithLogging(apiKey, hashUrl, reason = 'Manual retrieval') { try { const paymentLink = await getPaymentLink(apiKey, hashUrl);
console.log('Payment link retrieved:', { paymentLinkId: paymentLink.id, hashUrl: hashUrl, reason: reason, status: paymentLink.status, timestamp: new Date().toISOString() });
return paymentLink; } catch (error) { console.error('Failed to retrieve payment link:', { hashUrl: hashUrl, reason: reason, error: error.message, timestamp: new Date().toISOString() }); throw error; }}Testing
Section titled “Testing”Test Different Statuses
Section titled “Test Different Statuses”Test retrieval of payment links in different states:
// Test pending payment linkconst pendingLink = await getPaymentLink('your-api-key', 'pending-hash');
// Test completed payment linkconst completedLink = await getPaymentLink('your-api-key', 'completed-hash');
// Test cancelled payment linkconst cancelledLink = await getPaymentLink('your-api-key', 'cancelled-hash');Test Error Scenarios
Section titled “Test Error Scenarios”Test error handling:
// Test non-existent payment linktry { await getPaymentLink('your-api-key', 'non-existent-hash');} catch (error) { console.log('Expected error:', error.message);}
// Test invalid API key (404 ERR_ACCOUNT_API_KEY_NOT_FOUND)try { await getPaymentLink('invalid-key', 'valid-hash');} catch (error) { console.log('Expected error:', error.code, error.message);}Related Endpoints
Section titled “Related Endpoints”- List Payment Links - Retrieve multiple payment links
- Create Payment Link - Create a new payment link
- Cancel Payment Link - Cancel a payment link