Payment Links API
Automate payment link creation and management using the Omise Payment Links API. Perfect for businesses that need to generate many links, integrate with existing systems, or create dynamic payment experiences.
Overviewโ
The Payment Links API allows you to:
- Create payment links programmatically
- Retrieve link details and status
- Update link configuration
- List all payment links
- Track link performance
- Handle webhooks for payment events
Authenticationโ
All API requests require authentication using your secret key:
curl https://api.omise.co/links \
-u skey_test_xxx:
Important:
- Use
skey_test_xxxfor test mode - Use
skey_live_xxxfor live mode - Never expose secret keys in client-side code
- Keep secret keys secure
API Endpointsโ
Base URLโ
https://api.omise.co
Available Endpointsโ
| Method | Endpoint | Description |
|---|---|---|
| POST | /links | Create a payment link |
| GET | /links/:id | Retrieve a payment link |
| PATCH | /links/:id | Update a payment link |
| GET | /links | List all payment links |
Creating Payment Linksโ
Basic Creationโ
Create a simple payment link:
const omise = require('omise')({
secretKey: 'skey_test_xxx'
});
omise.links.create({
amount: 100000, // Amount in smallest currency unit (1,000.00 THB)
currency: 'thb',
title: 'Product Purchase',
description: 'Premium widget purchase',
multiple: true // Multi-use link
}, function(error, link) {
if (error) {
console.error('Error:', error);
} else {
console.log('Link created:', link.payment_uri);
}
});
Response:
{
"object": "link",
"id": "payl_test_5xyx8z94w46ixr9l",
"livemode": false,
"location": "/links/payl_test_5xyx8z94w46ixr9l",
"amount": 100000,
"currency": "thb",
"title": "Product Purchase",
"description": "Premium widget purchase",
"multiple": true,
"payment_uri": "https://pay.omise.co/links/payl_test_5xyx8z94w46ixr9l",
"used": false,
"charges": {
"object": "list",
"data": [],
"total": 0
},
"created_at": "2026-02-06T10:30:00Z"
}
Using cURLโ
curl https://api.omise.co/links \
-u skey_test_xxx: \
-d "amount=100000" \
-d "currency=thb" \
-d "title=Product Purchase" \
-d "description=Premium widget purchase" \
-d "multiple=true"
Using Pythonโ
import omise
omise.api_secret = 'skey_test_xxx'
link = omise.Link.create(
amount=100000,
currency='thb',
title='Product Purchase',
description='Premium widget purchase',
multiple=True
)
print(f"Link URL: {link.payment_uri}")
Using PHPโ
<?php
require_once 'vendor/autoload.php';
define('OMISE_SECRET_KEY', 'skey_test_xxx');
$link = OmiseLink::create([
'amount' => 100000,
'currency' => 'thb',
'title' => 'Product Purchase',
'description' => 'Premium widget purchase',
'multiple' => true
]);
echo "Link URL: " . $link['payment_uri'];
?>
Using Rubyโ
require 'omise'
Omise.api_key = 'skey_test_xxx'
link = Omise::Link.create({
amount: 100000,
currency: 'thb',
title: 'Product Purchase',
description: 'Premium widget purchase',
multiple: true
})
puts "Link URL: #{link.payment_uri}"
Request Parametersโ
Required Parametersโ
| Parameter | Type | Description |
|---|---|---|
amount | integer | Amount in smallest currency unit (e.g., satangs for THB) |
currency | string | Three-letter ISO currency code (e.g., thb, usd) |
title | string | Link title shown to customers (max 200 chars) |
Optional Parametersโ
| Parameter | Type | Default | Description |
|---|---|---|---|
description | string | null | Detailed description (max 1000 chars) |
multiple | boolean | false | Allow multiple uses (true) or single-use (false) |
metadata | object | Custom metadata (key-value pairs) |
Advanced Creation Examplesโ
Single-Use Payment Linkโ
Perfect for invoices:
omise.links.create({
amount: 250000, // 2,500.00 THB
currency: 'thb',
title: 'Invoice #INV-001',
description: 'Payment for consulting services',
multiple: false, // Single-use only
metadata: {
invoice_id: 'INV-001',
customer_id: 'CUST-123',
customer_email: 'customer@example.com'
}
}, function(error, link) {
if (!error) {
console.log('Invoice link:', link.payment_uri);
console.log('Link ID:', link.id);
}
});
Link with Metadataโ
Store custom information:
omise.links.create({
amount: 150000,
currency: 'thb',
title: 'Consultation Booking',
description: '1-hour consultation session',
multiple: true,
metadata: {
service_type: 'consultation',
duration: '60_minutes',
category: 'business',
booking_date: '2026-03-15',
time_slot: '14:00',
consultant_id: 'CONS-456'
}
}, function(error, link) {
if (!error) {
console.log('Booking link created');
// Store link.id in your database
}
});
Multi-Currency Linksโ
Create links in different currencies:
// USD link for international customers
omise.links.create({
amount: 5000, // $50.00
currency: 'usd',
title: 'International Product',
description: 'Available for global shipping',
multiple: true
}, function(error, usdLink) {
console.log('USD link:', usdLink.payment_uri);
});
// THB link for domestic customers
omise.links.create({
amount: 150000, // 1,500.00 THB
currency: 'thb',
title: 'Thailand Product',
description: 'เธชเธณเธซเธฃเธฑเธเธฅเธนเธเธเนเธฒเนเธเธเธฃเธฐเนเธเธจเนเธเธข',
multiple: true
}, function(error, thbLink) {
console.log('THB link:', thbLink.payment_uri);
});
Retrieving Payment Linksโ
Get Specific Linkโ
Retrieve a link by ID:
omise.links.retrieve('payl_test_5xyx8z94w46ixr9l', function(error, link) {
if (!error) {
console.log('Link status:', link.used ? 'Used' : 'Active');
console.log('Total charges:', link.charges.total);
console.log('Created:', link.created_at);
}
});
Using cURL:
curl https://api.omise.co/links/payl_test_5xyx8z94w46ixr9l \
-u skey_test_xxx:
Response:
{
"object": "link",
"id": "payl_test_5xyx8z94w46ixr9l",
"livemode": false,
"amount": 100000,
"currency": "thb",
"title": "Product Purchase",
"description": "Premium widget purchase",
"multiple": true,
"payment_uri": "https://pay.omise.co/links/payl_test_5xyx8z94w46ixr9l",
"used": false,
"charges": {
"object": "list",
"data": [
{
"object": "charge",
"id": "chrg_test_xxx",
"amount": 100000,
"status": "successful",
"created_at": "2026-02-06T11:00:00Z"
}
],
"total": 1
},
"created_at": "2026-02-06T10:30:00Z"
}
List All Linksโ
Get paginated list of all links:
omise.links.list({
limit: 20,
offset: 0,
order: 'reverse_chronological'
}, function(error, list) {
if (!error) {
console.log('Total links:', list.total);
list.data.forEach(function(link) {
console.log(`${link.title}: ${link.payment_uri}`);
});
}
});
Using cURL:
curl https://api.omise.co/links?limit=20&offset=0 \
-u skey_test_xxx:
List Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 20 | Number of links per page (max 100) |
offset | integer | 0 | Number of links to skip |
order | string | chronological | Sort order: chronological or reverse_chronological |
from | string | null | Filter links created from date (ISO 8601) |
to | string | null | Filter links created until date (ISO 8601) |
Updating Payment Linksโ
Update link details (limited fields):
omise.links.update('payl_test_5xyx8z94w46ixr9l', {
description: 'Updated product description with more details',
metadata: {
updated_at: '2026-02-06',
reason: 'price_change',
updated_by: 'admin'
}
}, function(error, link) {
if (!error) {
console.log('Link updated successfully');
}
});
Using cURL:
curl https://api.omise.co/links/payl_test_5xyx8z94w46ixr9l \
-X PATCH \
-u skey_test_xxx: \
-d "description=Updated product description"
Updatable Fields:
description- Link descriptionmetadata- Custom metadata
Non-Updatable Fields:
amount- Cannot change amountcurrency- Cannot change currencytitle- Cannot change titlemultiple- Cannot change type
Note: For significant changes, create a new link and disable the old one.
Practical Integration Examplesโ
Example 1: E-Commerce Product Linksโ
Generate links for each product:
const products = [
{ id: 'PROD-001', name: 'T-Shirt Red', price: 45000 },
{ id: 'PROD-002', name: 'T-Shirt Blue', price: 45000 },
{ id: 'PROD-003', name: 'T-Shirt Green', price: 45000 }
];
products.forEach(product => {
omise.links.create({
amount: product.price,
currency: 'thb',
title: product.name,
description: `Purchase ${product.name}`,
multiple: true,
metadata: {
product_id: product.id,
product_name: product.name,
category: 'apparel'
}
}, function(error, link) {
if (!error) {
// Store link.payment_uri in database
console.log(`${product.name}: ${link.payment_uri}`);
// Save to database
saveToDatabase({
product_id: product.id,
payment_link: link.payment_uri,
link_id: link.id
});
}
});
});
Example 2: Dynamic Invoice Generationโ
Create invoice links on-demand:
function createInvoiceLink(invoice) {
return new Promise((resolve, reject) => {
omise.links.create({
amount: invoice.total,
currency: invoice.currency,
title: `Invoice ${invoice.number}`,
description: `Payment for ${invoice.description}`,
multiple: false, // Single-use
metadata: {
invoice_number: invoice.number,
customer_id: invoice.customer_id,
customer_email: invoice.customer_email,
due_date: invoice.due_date,
items: JSON.stringify(invoice.items)
}
}, function(error, link) {
if (error) {
reject(error);
} else {
resolve(link);
}
});
});
}
// Usage
const invoice = {
number: 'INV-2026-001',
total: 350000, // 3,500.00 THB
currency: 'thb',
description: 'Web development services',
customer_id: 'CUST-123',
customer_email: 'customer@example.com',
due_date: '2026-03-06',
items: [
{ description: 'Homepage design', amount: 150000 },
{ description: 'Contact page', amount: 100000 },
{ description: 'Mobile responsive', amount: 100000 }
]
};
createInvoiceLink(invoice)
.then(link => {
console.log('Invoice link:', link.payment_uri);
// Send email to customer
sendInvoiceEmail(invoice.customer_email, {
invoice_number: invoice.number,
payment_link: link.payment_uri,
amount: invoice.total,
due_date: invoice.due_date
});
})
.catch(error => {
console.error('Error creating invoice link:', error);
});
Example 3: Event Registration Systemโ
Generate tickets with links:
const events = {
'workshop-2026': {
name: 'Web Development Workshop 2026',
tickets: [
{ type: 'early_bird', price: 150000, available: 50 },
{ type: 'regular', price: 200000, available: 100 },
{ type: 'vip', price: 350000, available: 20 }
]
}
};
async function generateEventLinks(eventId, event) {
const links = [];
for (const ticket of event.tickets) {
const link = await new Promise((resolve, reject) => {
omise.links.create({
amount: ticket.price,
currency: 'thb',
title: `${event.name} - ${ticket.type.replace('_', ' ').toUpperCase()} Ticket`,
description: `Register for ${event.name}`,
multiple: true,
metadata: {
event_id: eventId,
ticket_type: ticket.type,
tickets_available: ticket.available
}
}, (error, link) => {
if (error) reject(error);
else resolve(link);
});
});
links.push({
ticket_type: ticket.type,
link: link.payment_uri,
link_id: link.id
});
}
return links;
}
// Generate links for event
generateEventLinks('workshop-2026', events['workshop-2026'])
.then(links => {
console.log('Event links generated:');
links.forEach(l => {
console.log(`${l.ticket_type}: ${l.link}`);
});
});
Example 4: Subscription Payment Linksโ
Create monthly payment links:
function generateMonthlyLink(subscription) {
const currentDate = new Date();
const month = currentDate.toLocaleString('en-US', { month: 'long' });
const year = currentDate.getFullYear();
return omise.links.create({
amount: subscription.monthly_amount,
currency: 'thb',
title: `${subscription.plan_name} - ${month} ${year}`,
description: `Monthly payment for ${subscription.plan_name} subscription`,
multiple: false, // Each month gets unique link
metadata: {
subscription_id: subscription.id,
customer_id: subscription.customer_id,
plan: subscription.plan_name,
billing_month: `${year}-${currentDate.getMonth() + 1}`,
auto_generated: 'true'
}
});
}
// Example: Generate for all active subscriptions
const activeSubscriptions = [
{
id: 'SUB-001',
customer_id: 'CUST-123',
plan_name: 'Premium',
monthly_amount: 99000
},
{
id: 'SUB-002',
customer_id: 'CUST-456',
plan_name: 'Business',
monthly_amount: 299000
}
];
activeSubscriptions.forEach(subscription => {
generateMonthlyLink(subscription)
.then(link => {
console.log(`Link for ${subscription.customer_id}:`, link.payment_uri);
// Email customer
emailCustomer(subscription.customer_id, {
subject: 'Monthly Subscription Payment',
payment_link: link.payment_uri
});
})
.catch(error => {
console.error(`Error for ${subscription.id}:`, error);
});
});
Example 5: Bulk Link Generationโ
Generate many links efficiently:
async function bulkCreateLinks(items) {
const results = {
successful: [],
failed: []
};
// Process in batches to avoid rate limits
const batchSize = 10;
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const promises = batch.map(item =>
new Promise((resolve) => {
omise.links.create({
amount: item.amount,
currency: 'thb',
title: item.title,
description: item.description,
multiple: true,
metadata: item.metadata || {}
}, (error, link) => {
if (error) {
resolve({ success: false, item, error });
} else {
resolve({ success: true, item, link });
}
});
})
);
const batchResults = await Promise.all(promises);
batchResults.forEach(result => {
if (result.success) {
results.successful.push(result);
} else {
results.failed.push(result);
}
});
// Rate limiting delay
if (i + batchSize < items.length) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
return results;
}
// Usage
const products = [
{ title: 'Product A', amount: 50000, description: 'Description A' },
{ title: 'Product B', amount: 75000, description: 'Description B' },
{ title: 'Product C', amount: 100000, description: 'Description C' },
// ... more products
];
bulkCreateLinks(products)
.then(results => {
console.log(`Successfully created: ${results.successful.length}`);
console.log(`Failed: ${results.failed.length}`);
// Export successful links
const csv = results.successful.map(r =>
`${r.item.title},${r.link.payment_uri}`
).join('\n');
console.log('\nCSV Export:\n', csv);
});
Webhooks Integrationโ
Receive real-time notifications about payment link events:
Setting Up Webhooksโ
- Configure webhook endpoint in dashboard
- Listen for payment link events
- Verify webhook signatures
- Process events
Webhook Eventsโ
const express = require('express');
const app = express();
app.post('/webhooks/omise', express.json(), (req, res) => {
const event = req.body;
switch(event.key) {
case 'charge.create':
handleChargeCreated(event.data);
break;
case 'charge.complete':
handleChargeCompleted(event.data);
break;
case 'charge.failed':
handleChargeFailed(event.data);
break;
}
res.sendStatus(200);
});
function handleChargeCompleted(charge) {
// Check if charge is from a payment link
if (charge.source && charge.source.type === 'link') {
const linkId = charge.source.id;
console.log('Payment received for link:', linkId);
// Retrieve link details
omise.links.retrieve(linkId, (error, link) => {
if (!error) {
const metadata = link.metadata;
// Process based on metadata
if (metadata.invoice_id) {
markInvoicePaid(metadata.invoice_id);
} else if (metadata.product_id) {
fulfillOrder(metadata.product_id, charge);
}
// Send confirmation email
sendConfirmationEmail(charge.customer_email, {
amount: charge.amount,
description: link.title
});
}
});
}
}
app.listen(3000);
Webhook Signature Verificationโ
Verify webhook authenticity:
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const computed = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return computed === signature;
}
app.post('/webhooks/omise', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['omise-signature'];
const secret = process.env.WEBHOOK_SECRET;
if (!verifyWebhook(req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// Process event...
res.sendStatus(200);
});
Error Handlingโ
Common Errorsโ
omise.links.create({
amount: 100000,
currency: 'thb',
title: 'Product'
}, function(error, link) {
if (error) {
switch(error.code) {
case 'invalid_amount':
console.error('Invalid amount:', error.message);
break;
case 'invalid_currency':
console.error('Currency not supported:', error.message);
break;
case 'authentication_failure':
console.error('Invalid API key:', error.message);
break;
default:
console.error('Error:', error.message);
}
}
});
Error Response Formatโ
{
"object": "error",
"location": "https://www.omise.co/api-errors#authentication-failure",
"code": "authentication_failure",
"message": "authentication failed"
}
Common Error Codesโ
| Code | Description | Solution |
|---|---|---|
authentication_failure | Invalid API key | Check your secret key |
invalid_amount | Amount is invalid | Use positive integer in smallest unit |
invalid_currency | Unsupported currency | Use supported currency code |
invalid_card | Card validation failed | Check card details |
insufficient_fund | Insufficient balance | Customer needs to add funds |
failed_processing | Payment processing failed | Retry or contact support |
Retry Logicโ
Implement exponential backoff:
async function createLinkWithRetry(data, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
const link = await new Promise((resolve, reject) => {
omise.links.create(data, (error, link) => {
if (error) reject(error);
else resolve(link);
});
});
return link;
} catch (error) {
lastError = error;
// Don't retry certain errors
if (error.code === 'authentication_failure' ||
error.code === 'invalid_amount') {
throw error;
}
// Exponential backoff: 1s, 2s, 4s
const delay = Math.pow(2, i) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
Rate Limitsโ
Limitsโ
- 60 requests per minute per API key
- 1000 requests per hour per API key
Handling Rate Limitsโ
const rateLimit = {
requests: 0,
resetAt: Date.now() + 60000
};
function checkRateLimit() {
if (Date.now() > rateLimit.resetAt) {
rateLimit.requests = 0;
rateLimit.resetAt = Date.now() + 60000;
}
if (rateLimit.requests >= 60) {
const waitTime = rateLimit.resetAt - Date.now();
throw new Error(`Rate limit exceeded. Try again in ${waitTime}ms`);
}
rateLimit.requests++;
}
async function createLinkSafe(data) {
checkRateLimit();
return new Promise((resolve, reject) => {
omise.links.create(data, (error, link) => {
if (error) reject(error);
else resolve(link);
});
});
}
Best Practicesโ
1. Use Metadata Effectivelyโ
Store all relevant information:
omise.links.create({
amount: 100000,
currency: 'thb',
title: 'Product Purchase',
metadata: {
// Business context
order_id: 'ORD-123',
customer_id: 'CUST-456',
// Product details
product_id: 'PROD-789',
product_sku: 'WIDGET-001',
// Fulfillment info
shipping_required: 'true',
warehouse: 'BKK-01',
// Tracking
campaign: 'summer-sale',
referrer: 'social-media',
// Contact
customer_email: 'customer@example.com',
customer_phone: '+66812345678'
}
});
2. Implement Idempotencyโ
Prevent duplicate links:
const linkCache = new Map();
async function createLinkIdempotent(key, data) {
// Check if link already exists
if (linkCache.has(key)) {
return linkCache.get(key);
}
// Create new link
const link = await new Promise((resolve, reject) => {
omise.links.create(data, (error, link) => {
if (error) reject(error);
else resolve(link);
});
});
// Cache the link
linkCache.set(key, link);
return link;
}
// Usage
const invoiceId = 'INV-001';
const link = await createLinkIdempotent(invoiceId, {
amount: 100000,
currency: 'thb',
title: `Invoice ${invoiceId}`,
metadata: { invoice_id: invoiceId }
});
3. Store Link IDsโ
Always store link IDs in your database:
async function createAndStoreLinkInfo(orderData) {
// Create link
const link = await createLink(orderData);
// Store in database
await database.orders.update(orderData.id, {
payment_link_id: link.id,
payment_link_url: link.payment_uri,
link_created_at: link.created_at
});
return link;
}
4. Handle Webhooks Properlyโ
app.post('/webhooks', async (req, res) => {
// Respond quickly
res.sendStatus(200);
// Process asynchronously
processWebhook(req.body).catch(error => {
console.error('Webhook processing error:', error);
// Log for manual review
});
});
async function processWebhook(event) {
// Verify signature
// Prevent duplicate processing
// Update database
// Send notifications
}
5. Monitor Link Performanceโ
Track link usage:
async function getLinkStats(linkId) {
const link = await omise.links.retrieve(linkId);
return {
total_charges: link.charges.total,
successful_payments: link.charges.data.filter(c =>
c.status === 'successful'
).length,
failed_payments: link.charges.data.filter(c =>
c.status === 'failed'
).length,
total_revenue: link.charges.data
.filter(c => c.status === 'successful')
.reduce((sum, c) => sum + c.amount, 0),
conversion_rate: calculateConversionRate(link)
};
}
Testingโ
Test Modeโ
Use test API keys:
const omise = require('omise')({
secretKey: 'skey_test_xxx' // Test key
});
Test Cardsโ
// Test successful payment
const testCardSuccess = {
number: '4242424242424242',
name: 'Test User',
expiration_month: 1,
expiration_year: 2026,
security_code: '123'
};
// Test failed payment
const testCardFailed = {
number: '4000000000000002',
name: 'Test User',
expiration_month: 1,
expiration_year: 2026,
security_code: '123'
};
Test Webhooksโ
Use tools like ngrok:
# Start ngrok
ngrok http 3000
# Use ngrok URL in dashboard webhook settings
https://abc123.ngrok.io/webhooks/omise
Frequently Asked Questionsโ
Q: Can I create links without amount (customer decides)?
A: No, the API currently requires a fixed amount. For flexible amounts, use the dashboard interface.
Q: How do I delete a payment link?
A: Links cannot be deleted via API. They can only be marked as inactive by reaching their use limit or expiration.
Q: Can I customize the payment page design via API?
A: No, design customization is only available through the dashboard. The API focuses on link creation and management.
Q: Is there a limit to how many links I can create?
A: Standard accounts can have 100 active links. Contact support for higher limits. Inactive/used links don't count toward the limit.
Q: How do I track which channel a payment came from?
A: Use metadata to tag links with channel information, or create separate links for each channel.
Q: Can I create recurring payment links? A: Payment Links are for one-time payments. For recurring charges, use the Subscriptions API.
Q: How quickly are links available after creation?
A: Links are immediately available after creation. No propagation delay.
Q: Can I update the amount of an existing link?
A: No, amounts cannot be changed. Create a new link with the updated amount.
Q: Do I need to create separate links for different payment methods?
A: No, one link supports all payment methods enabled in your account. Customers choose their preferred method.
Q: How do I know when a payment is made through a link?
A: Use webhooks for real-time notifications, or poll the charges list using the API.
Q: Can I use links in mobile apps?
A: Yes, open the payment_uri in a webview or external browser. The page is mobile-optimized.
Q: What happens if I exceed rate limits?
A: You'll receive a 429 Too Many Requests error. Implement exponential backoff and retry.
Next Stepsโ
- Payment Links Overview - Learn about Payment Links features
- Creating Links - Create links via dashboard
- Webhooks Guide - Set up webhook notifications
- API Reference - Complete API documentation
Additional Resourcesโ
- Omise Libraries - Official SDK libraries
- Testing Guide - Testing strategies
- Security Best Practices - Secure your integration
- Dashboard Guide - Using the dashboard
Need help? Contact our support team at support@omise.co or visit the Help Center.