Documentation
Clink SDK Documentation
Last updated April 8, 2026
This guide is focused on developers integrating clink-sdk. It keeps the docs centered on what your customers need to ship payments, and leaves out internal operator details that only Clink needs to manage behind the scenes.
Get an API key
Before you can start testing the SDK, request your API key from Clink.
Apply for an API keyInstallation
npm install clink-sdkRequirements: Node.js >= 24
Authentication
All SDK requests require your API key.
Pass it as secretKey in the constructor.
SDK quickstart
import Clink from 'clink-sdk';
const clink = new Clink({
secretKey: process.env.CLINK_SECRET_KEY,
webhookSecret: process.env.CLINK_WEBHOOK_SECRET,
});
// Create a payment
const payment = await clink.payments.create({
amount: 10,
currency: 'USDC',
localCurrency: 'NGN',
callbackUrl: 'https://yourapp.com/webhooks/clink',
customerEmail: 'buyer@example.com',
metadata: { orderId: 'order_123' },
});
// Show payment.stellarAddress and payment.memo to the customer
// Verify after the customer sends USDC
const updated = await clink.payments.verify(payment.id);
console.log(updated.status); // 'settled'SDK configuration
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| secretKey | string | Yes | - | Your Clink API key |
| webhookSecret | string | No | secretKey | Secret used to verify webhook payloads |
| environment | 'testnet' | 'mainnet' | No | - | Select the Clink environment to work against |
Payments
Use the payments API to create payment instructions, verify incoming transfers, and inspect payment history from your app.
Create a payment
Creates a new pending payment and returns the Stellar address and memo to show your customer.
SDK:
const payment = await clink.payments.create({
amount: 10,
currency: 'USDC',
localCurrency: 'NGN',
callbackUrl: 'https://yourapp.com/webhooks/clink',
description: 'Pro plan subscription',
customerEmail: 'buyer@example.com',
metadata: { orderId: 'order_123' },
});Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
| amount | number | Yes | Amount in USDC |
| currency | 'USDC' | Yes | Must be 'USDC' |
| localCurrency | string | Yes | Payout currency - NGN, GHS, KES, or UGX |
| callbackUrl | string | Yes | HTTPS URL Clink will POST webhook events to |
| description | string | No | Payment description |
| customerEmail | string | No | Customer email address |
| metadata | object | No | Arbitrary key-value data returned in webhooks |
Response: Payment object
Verify a payment
Checks Stellar for an incoming USDC transaction matching the payment. If found, triggers settlement and returns the updated payment.
const payment = await clink.payments.verify('pay_abc123');Response: Payment object
List payments
// All payments
const payments = await clink.payments.list();
// With filters
const payments = await clink.payments.list({
status: 'pending',
limit: 20,
});Query parameters:
| Parameter | Type | Description |
|---|---|---|
| status | string | Filter by status: pending, confirmed, settled, expired, failed |
| limit | number | Max results to return (default: 20) |
Response: Array of Payment objects
Payment object
{
"id": "pay_a1b2c3d4e5f6g7h8i9",
"stellarAddress": "GCEUHLTXIODT3XXIKZZKHZWX5A2H54BGKGKZPWRZZEZBHOK26C7OEEWR",
"memo": "clink-pay_a1b2c3d4e5f6g7h8i9",
"amount": 10,
"currency": "USDC",
"localCurrency": "NGN",
"localAmount": 16000,
"status": "settled",
"description": "Pro plan subscription",
"customerEmail": "buyer@example.com",
"stellarTxHash": "abc123...",
"callbackUrl": "https://yourapp.com/webhooks/clink",
"metadata": { "orderId": "order_123" },
"expiresAt": "2025-01-01T00:30:00.000Z",
"createdAt": "2025-01-01T00:00:00.000Z",
"settledAt": "2025-01-01T00:05:00.000Z"
}Fields:
| Field | Type | Description |
|---|---|---|
| id | string | Unique payment ID |
| stellarAddress | string | Stellar address the customer sends USDC to |
| memo | string | Memo the customer must include with their transaction |
| amount | number | USDC amount |
| currency | string | Always USDC |
| localCurrency | string | Payout currency |
| localAmount | number | Settled local currency amount (present after settlement) |
| status | string | Current payment status |
| stellarTxHash | string | Stellar transaction hash (present after confirmation) |
| metadata | object | Your custom metadata |
| expiresAt | string | ISO 8601 expiry time |
| createdAt | string | ISO 8601 creation time |
| settledAt | string | ISO 8601 settlement time (present after settlement) |
Important: Always instruct your customer to include the memo exactly when sending USDC. Without the memo, Clink cannot match the transaction to the payment.
Payment lifecycle
pending --> confirmed --> settled
|
+--> expired
|
+--> failed| Status | Description |
|---|---|
| pending | Payment created, waiting for USDC on Stellar |
| confirmed | USDC received on Stellar, settlement in progress |
| settled | Local currency payout complete |
| expired | USDC not received before the payment expires |
| failed | Settlement failed after USDC was received |
Webhooks
Clink sends a signed POST request to your callbackUrl whenever a payment status changes.
Webhook events
| Event | Fired when |
|---|---|
| payment.confirmed | USDC arrives on Stellar |
| payment.settled | Local currency payout is complete |
| payment.failed | Settlement fails |
| payment.expired | Payment expires without receiving USDC |
Webhook payload
{
"event": "payment.settled",
"data": {
"id": "pay_a1b2c3d4e5f6g7h8i9",
"status": "settled",
"localAmount": 16000,
"metadata": { "orderId": "order_123" }
},
"signature": "a3f9c1e2b4d8..."
}The signature is also sent as the x-clink-signature request header.
Verifying signatures
Always verify the signature before processing a webhook. Ignore any webhook with an invalid signature.
app.post('/webhooks/clink', express.json(), async (req, res) => {
const valid = clink.webhooks.verify({
payload: req.body,
signature: req.headers['x-clink-signature'] as string,
secret: process.env.CLINK_WEBHOOK_SECRET,
});
if (!valid) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { event, data } = req.body;
switch (event) {
case 'payment.settled':
await fulfillOrder(data.metadata.orderId);
break;
case 'payment.expired':
break;
case 'payment.failed':
break;
}
res.json({ received: true });
});Webhook response: Always return 200 quickly. Clink retries failed deliveries up to 3 times with exponential backoff.
Error handling
All SDK errors follow this shape:
{
"error": "ERROR_CODE",
"message": "Human-readable description",
"details": {}
}Common codes:
| Code | HTTP status | Description |
|---|---|---|
| INVALID_API_KEY | 401 | Missing or invalid API key |
| INVALID_PAYMENT_REQUEST | 400 | Invalid payment parameters or duplicate email |
| INVALID_CONFIGURATION | 400 | Invalid SDK configuration |
| PAYMENT_NOT_FOUND | 404 | Payment ID does not exist |
| PAYMENT_EXPIRED | 410 | Payment has expired |
| INVALID_SIGNATURE | 401 | Webhook signature verification failed |
SDK error handling:
import Clink, { ClinkError } from 'clink-sdk';
try {
const payment = await clink.payments.create({ ... });
} catch (error) {
if (error instanceof ClinkError) {
console.error(error.code);
console.error(error.message);
console.error(error.details);
}
}Supported currencies
| Code | Currency | Country |
|---|---|---|
| NGN | Nigerian Naira | Nigeria |
| GHS | Ghanaian Cedi | Ghana |
| KES | Kenyan Shilling | Kenya |
| UGX | Ugandan Shilling | Uganda |
Environment variables
| Variable | Description |
|---|---|
| CLINK_SECRET_KEY | Your Clink API key |
| CLINK_WEBHOOK_SECRET | Secret for verifying webhook payloads |
The SDK can read these values from your environment so you do not have to hardcode them in your application.