Payment Links are the easiest way to start collecting payments with Tsara — no code required.
You can create them directly from your dashboard or via API, then share the generated URL with customers to receive payments in NGN or USDC.
Overview
- Create a payment link via API or dashboard.
- Share the link via WhatsApp, email, or your website.
- Receive instant payments (fiat or stablecoin).
- Get notified in real-time through webhooks.
Each payment link is unique and can include metadata, customer details, and custom redirect URLs.
Create a Payment Link
Generate a new payment link that can be shared with customers.
Endpoint
POST /payment-links
Headers
| Header | Value | Required |
|---|---|---|
Authorization | Bearer YOUR_SECRET_KEY | Yes |
Content-Type | application/json | Yes |
Request Parameters
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
amount | number | Yes | Payment amount (minor units for NGN, e.g., 50000 = ₦500.00) | 50000 |
currency | string | Yes | Currency code. Accepts: NGN, USDC | "NGN" |
title | string | Yes | Payment link title shown to customers (max 100 chars) | "Invoice #1001" |
description | string | No | Additional details shown on payment page (max 500 chars) | "Payment for order #1001" |
expires_at | string | No | ISO 8601 datetime when link expires. If not provided, link never expires | "2025-12-31T23:59:59Z" |
success_url | string | No | URL to redirect customer after successful payment | "https://example.com/success" |
cancel_url | string | No | URL to redirect customer if payment is cancelled | "https://example.com/cancel" |
fields | array | No | Additional fields to collect from customer. Options: "Address", "Phone", "Email" | ["Address", "Phone"] |
metadata | object | No | Custom key-value data for your reference (max 10 keys, 500 chars per value) | {"order_id": "A-1001"} |
slug | string | No | Custom URL slug (alphanumeric, hyphen, underscore only, max 50 chars). If not provided, random slug is generated | "invoice-1001" |
Request Example
{
"amount": 50000,
"currency": "NGN",
"title": "Invoice #1001",
"description": "Payment for order #1001",
"expires_at": "2025-12-31T23:59:59Z",
"success_url": "https://example.com/success",
"cancel_url": "https://example.com/cancel",
"fields": ["Address", "Phone"],
"metadata": {
"order_id": "A-1001",
"customer_id": "cus_123"
},
"slug": "invoice-1001"
}curl -X POST "https://sandbox.tsara.ng/v1/payment-links" \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 50000,
"currency": "NGN",
"title": "Invoice #1001",
"description": "Payment for order #1001",
"expires_at": "2025-12-31T23:59:59Z",
"success_url": "https://example.com/success",
"cancel_url": "https://example.com/cancel",
"fields": ["Address", "Phone"],
"metadata": {
"order_id": "A-1001"
},
"slug": "invoice-1001"
}'Response
{
"status": "success",
"status_code": 200,
"message": "Payment Link Created",
"url": "https://pay.usetsara.com/invoice-1001",
"data": {
"uid": "uid_8658062823",
"id": "plink_8658062823",
"url": "https://pay.usetsara.com/invoice-1001",
"amount": 50000,
"currency": "NGN",
"title": "Invoice #1001",
"status": "active",
"expires_at": "2025-12-31T23:59:59Z",
"created_at": "2025-01-31T10:30:00Z"
}
}Response Fields
| Field | Type | Description |
|---|---|---|
status | string | Request status. Always "success" for successful requests |
status_code | number | HTTP status code. 200 for success |
message | string | Human-readable message |
url | string | Direct payment URL to share with customers |
data.uid | string | Unique identifier (legacy format, use id instead) |
data.id | string | Payment link ID (use this for API calls) |
data.url | string | Payment URL (same as top-level url) |
data.amount | number | Payment amount |
data.currency | string | Currency code |
data.title | string | Payment link title |
data.status | string | Link status. Options: active, disabled, expired |
data.expires_at | string | Expiration datetime (null if no expiration) |
data.created_at | string | Creation timestamp |
Field Clarification: uid vs id
uid- Legacy identifier format (e.g.,uid_8658062823)id- Current identifier format (e.g.,plink_8658062823)- Both refer to the same payment link
- Use
idfor all API operations (retrieve, disable, etc.)
💡 The returned url can be shared directly with customers to collect payments.
Error Responses
{
"success": false,
"status_code": 400,
"error": {
"code": "validation_error",
"message": "Invalid request parameters",
"details": [
{
"field": "amount",
"message": "Amount must be greater than 0"
}
]
}
}Common Errors
| Status Code | Error Code | Description |
|---|---|---|
| 400 | validation_error | Invalid request parameters (check details array) |
| 400 | invalid_currency | Currency not supported. Use NGN or USDC |
| 400 | slug_already_exists | Custom slug is already in use |
| 401 | unauthorized | Invalid or missing API key |
| 429 | rate_limit_exceeded | Too many requests. Retry after delay |
Retrieve a Payment Link
Get details of a specific payment link by its ID.
Endpoint
GET /payment-links?id={payment_link_id}
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Payment link ID (format: plink_* or uid_*) |
Example Request
curl -X GET "https://sandbox.tsara.ng/v1/payment-links?id=plink_8658062823" \
-H "Authorization: Bearer YOUR_SECRET_KEY"Response
{
"success": true,
"status": "success",
"status_code": 200,
"message": "Payment link retrieved",
"data": {
"id": "plink_8658062823",
"uid": "uid_8658062823",
"amount": 50000,
"currency": "NGN",
"title": "Invoice #1001",
"description": "Payment for order #1001",
"status": "active",
"url": "https://pay.usetsara.com/invoice-1001",
"success_url": "https://example.com/success",
"cancel_url": "https://example.com/cancel",
"fields": ["Address", "Phone"],
"metadata": {
"order_id": "A-1001"
},
"expires_at": "2025-12-31T23:59:59Z",
"created_at": "2025-01-31T10:30:00Z",
"updated_at": "2025-01-31T10:30:00Z",
"payments_count": 0,
"total_paid": 0
}
}Response Fields
| Field | Type | Description |
|---|---|---|
payments_count | number | Number of successful payments received through this link |
total_paid | number | Total amount paid through this link (in minor units) |
Status Values
| Status | Description |
|---|---|
active | Link is active and accepting payments |
disabled | Link has been manually disabled |
expired | Link has passed its expiration date |
completed | Link has received payment (if single-use) |
List Payment Links
Retrieve all payment links with pagination support.
Endpoint
GET /payment-links?page={page}&limit={limit}
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page | number | No | 1 | Page number (starts at 1) |
limit | number | No | 20 | Items per page (max: 100) |
status | string | No | - | Filter by status: active, disabled, expired |
currency | string | No | - | Filter by currency: NGN, USDC |
Example Request
curl -X GET "https://sandbox.tsara.ng/v1/payment-links?page=1&limit=20&status=active" \
-H "Authorization: Bearer YOUR_SECRET_KEY"Response
{
"success": true,
"status": "success",
"status_code": 200,
"message": "Payment links retrieved",
"data": [
{
"id": "plink_001",
"uid": "uid_001",
"amount": 25000,
"currency": "NGN",
"title": "Invoice #1001",
"status": "active",
"url": "https://pay.usetsara.com/invoice-1001",
"created_at": "2025-01-30T10:00:00Z",
"payments_count": 0,
"total_paid": 0
},
{
"id": "plink_002",
"uid": "uid_002",
"amount": 100000,
"currency": "NGN",
"title": "Product Purchase",
"status": "completed",
"url": "https://pay.usetsara.com/product-xyz",
"created_at": "2025-01-29T15:30:00Z",
"payments_count": 1,
"total_paid": 100000
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 2,
"total_pages": 1,
"has_more": false
}
}Pagination Fields
| Field | Type | Description |
|---|---|---|
page | number | Current page number |
limit | number | Items per page |
total | number | Total number of payment links |
total_pages | number | Total number of pages |
has_more | boolean | Whether there are more pages available |
Disable a Payment Link
Disable a payment link to stop accepting payments. This action is irreversible.
Endpoint
POST /payment-links/status
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
uid | string | Yes | Payment link ID (accepts both uid_* and plink_* formats) |
status | string | Yes | Must be "disabled" |
Example Request
curl -X POST "https://sandbox.tsara.ng/v1/payment-links/status" \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "disabled",
"uid": "plink_8658062823"
}'Response
{
"success": true,
"status": "success",
"status_code": 200,
"message": "Payment link disabled",
"data": {
"id": "plink_8658062823",
"uid": "uid_8658062823",
"status": "disabled",
"disabled_at": "2025-01-31T12:00:00Z"
}
}⚠️ Note: Disabled payment links cannot be re-enabled. Create a new payment link if needed.
Webhook Notifications
When a customer completes payment through a link, Tsara sends a payment.success or payment.failed event to your configured webhook endpoint.
Event Types
| Event | Description |
|---|---|
payment.success | Payment completed successfully |
payment.failed | Payment attempt failed |
payment.pending | Payment is being processed (for bank transfers) |
Example Payload: payment.success
{
"id": "evt_456",
"type": "payment.success",
"created_at": "2025-01-31T12:00:00Z",
"data": {
"payment": {
"id": "pay_001",
"payment_link_id": "plink_8658062823",
"amount": 50000,
"currency": "NGN",
"reference": "order_001",
"status": "success",
"customer": {
"email": "[email protected]",
"phone": "08012345678",
"address": "123 Main St, Lagos"
},
"metadata": {
"order_id": "A-1001"
},
"paid_at": "2025-01-31T11:59:30Z"
}
}
}Webhook Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique event ID |
type | string | Event type |
created_at | string | Event timestamp |
data.payment.id | string | Payment transaction ID |
data.payment.payment_link_id | string | Payment link that generated this payment |
data.payment.reference | string | Your custom reference (from metadata) |
data.payment.customer | object | Customer details collected during payment |
data.payment.metadata | object | Metadata you attached to the payment link |
Verifying Webhook Signatures
Always verify webhook signatures to ensure requests are from Tsara:
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha512', secret)
.update(payload)
.digest('hex');
return signature === expected;
}
const isValid = verifyWebhook(
req.body,
req.headers['x-tsara-signature'],
process.env.TSARA_WEBHOOK_SECRET
);
if (!isValid) {
return res.status(400).send('Invalid signature');
}Use Cases & Examples
Single-Use Payment Link
Create a payment link that automatically disables after one successful payment:
{
"amount": 150000,
"currency": "NGN",
"title": "Invoice #INV-2025-001",
"description": "Website Design - Final Payment",
"metadata": {
"invoice_id": "INV-2025-001",
"auto_disable": "true"
}
}Monitor the payment.success webhook and disable the link:
app.post('/webhooks/tsara', (req, res) => {
const event = req.body;
if (event.type === 'payment.success') {
const autoDisable = event.data.payment.metadata.auto_disable;
if (autoDisable === 'true') {
await disablePaymentLink(event.data.payment.payment_link_id);
}
}
res.sendStatus(200);
});Subscription Payment Link
Create a reusable link for recurring payments:
{
"amount": 5000,
"currency": "NGN",
"title": "Monthly Subscription - Pro Plan",
"description": "Renews monthly",
"slug": "pro-plan-monthly",
"metadata": {
"plan": "pro",
"billing_cycle": "monthly"
}
}USDC Payment Link
Accept stablecoin payments:
{
"amount": 50,
"currency": "USDC",
"title": "USDC Payment",
"description": "Pay 50 USDC",
"metadata": {
"crypto_payment": "true"
}
}Note: USDC amounts are in full units (e.g., 50 = 50 USDC), not minor units.
Tips & Best Practices
-
Always verify payment status using the Verify Payment API (
GET /payments?reference={reference}) before fulfilling orders, even after receiving a webhook. -
Use metadata to track payments. Add order IDs, invoice numbers, or any custom data you need:
"metadata": { "order_id": "ORD-123", "customer_id": "cus_456", "source": "website" } -
Handle webhooks asynchronously. Process them in a queue to avoid blocking your webhook endpoint:
app.post('/webhooks/tsara', (req, res) => { queue.add('process-payment', req.body); res.sendStatus(200); }); -
Set expiration dates for time-sensitive payments:
"expires_at": "2025-02-05T23:59:59Z" -
Use custom slugs for branded payment URLs:
"slug": "invoice-jan-2025-acme-corp"Results in:
https://pay.usetsara.com/invoice-jan-2025-acme-corp -
Test in sandbox before going live. Use sandbox API keys and test the full payment flow.
-
Monitor link performance using the
payments_countandtotal_paidfields from the retrieve endpoint. -
Collect additional information using the
fieldsparameter:"fields": ["Address", "Phone", "Email"]This data will be included in webhook payloads.
Troubleshooting
Payment link shows as expired immediately
Cause: expires_at is in the past or incorrect format.
Solution: Use ISO 8601 format and ensure the date is in the future:
"expires_at": "2025-12-31T23:59:59Z"Custom slug not working
Cause: Slug contains invalid characters or is already in use.
Solution: Use only alphanumeric characters, hyphens, and underscores. Check if slug is available first:
curl -X GET "https://sandbox.tsara.ng/v1/payment-links?slug=my-custom-slug"Webhook not receiving events
Cause: Webhook URL not configured or signature validation failing.
Solution:
- Configure webhook URL in dashboard: Settings → Webhooks
- Verify signature using HMAC-SHA512
- Ensure endpoint returns 200 OK within 10 seconds
Amount showing incorrectly
Cause: NGN amounts should be in kobo (minor units).
Solution:
- For ₦500.00, use
50000(500 × 100) - For USDC, use full units:
50= 50 USDC
Related Pages
- Checkout — Accept payments directly in your app or website
- Verify Payments — Confirm transaction status via API
- Webhooks — Learn how to handle payment events securely
- Webhooks Security — Signature verification guide
- Errors & Status Codes — Complete error reference