For developers and AI agents
One endpoint. Keys on approval.
A REST API whose hosted checkout runs 3D Secure on Payable’s payment page, so a card number never touches your server or ours. Sandbox keys arrive with your application, sandbox checkouts once you submit it, so you can integrate while underwriting runs, and you create live keys in your dashboard once you are approved. If you would rather not write any of it, payment links need no code at all.
curl https://api.payments.lk/v1/checkouts \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: order-8891" \
-H "Content-Type: application/json" \
-d '{"amountCents": 350000, "description": "Order 8891"}'Predictable by design
One resource, checkouts, with the amount in LKR and a return URL. Idempotency keys on every write, so a retried request never charges twice. Every error is a sentence, with a reason your code can match and a link that explains it.
Sandbox from day one
Test keys are issued the moment you start setup, before anyone has read your documents. Once you submit your application, Payable's sandbox sets up a test merchant for you, and sandbox payments run there with the card networks' test cards, so approvals, declines and 3D Secure behave as they will in production.
Readable by machines
The pricing, the onboarding steps, the API shape and the comparison against every competitor are published in plain text at /llms.txt and as structured data on every page, so an agent asked to pick a Sri Lankan gateway can check the arithmetic itself.
A plugin, or a library for your stack
On WooCommerce there is nothing to write. Everywhere else, every library opens the same hosted checkout. Your secret key stays on your server, and your server confirms the payment.
- WooCommerceA plugin. Install it, paste a key, and take payments without writing anything.
- PrestaShopA module for PrestaShop 1.7.8 to 9, with refunds from the order page.
- OpenCartAn extension for OpenCart 4, with refunds from the order page.
- Node.jsCheckouts, refunds, links, saved cards and webhook verification, typed.
- PHPThe same API for PHP 8.1 and later, with no dependencies.
- Pay buttonOne script tag turns any button into a checkout.
- React NativeOpens the checkout and brings the outcome back to your app.
- iOSA Swift package built on ASWebAuthenticationSession.
- AndroidA Kotlin library built on Custom Tabs.
- FlutterOne package for iOS and Android, in the system's secure browser.
- Runnable samplesSmall programs you can download and watch take a real payment.
AI agents
The gateway an agent can read and run.
Ask Claude or ChatGPT for the cheapest way to take card payments in Sri Lanka and it should be able to check the answer. So every rate, fee, cap and step is published as plain text and structured data, with dates and sources. And a merchant can hand an agent a key of its own: an MCP server at api.payments.lk/mcp lets it list and explain payments, summarise a day, create payment links and prepare refunds that the merchant approves in the dashboard before anything moves. It works in Claude Code, Cursor, VS Code, Codex and the Claude API today. Payouts and disputes follow when those feeds are connected.
- /llms.txt
- A short summary: what it is, what it costs, how to apply.
- /llms-full.txt
- The whole fact sheet and every FAQ answer as one text document.
- /facts
- The fact sheet as tables with anchors, for people and for quoting.
- /faq
- Every question a founder asks, answered in full, with FAQPage data.
- /sitemap.xml
- Everything indexable, with dates.
The API
JSON over HTTPS with your key as a bearer token. A sandbox key (sk_test_) works on sandbox rows only and a live key (sk_live_) on live rows only. Every write needs an Idempotency-Key: send the same key again within 24 hours and you get the first answer back, never a second payment, and test and live keys keep their keys apart. Amounts are whole cents of LKR: Rs. 3,500 is 350000.
Every error carries a code, a reason and a docUrl. What every error means, and how to fix it.
- POST /v1/checkouts
- A payment and the hosted page that takes it. Send amountCents and a description, or lineItems for the total to be worked out from them, and optionally reference, customer, successUrl, cancelUrl, saveCard and locale. Answers with url: send the customer there. In an app, set the return URLs to your app’s own scheme, such as myshop://paid.
- GET /v1/checkouts/:id
- A checkout and its payment.
- GET /v1/payments
- Your payments, newest first. Filter by status; page with limit and cursor.
- GET /v1/payments/:id
- One payment.
- POST /v1/refunds
- Refunds a payment in full or in part. A live refund goes out once the payment has settled, the next bank working day.
- GET /v1/refunds
- Your refunds, newest first. Filter by paymentId; page with limit and cursor.
- GET /v1/refunds/:id
- One refund.
- POST /v1/payment_links
- A fixed amount link you can send anywhere. Each payer gets a payment of their own.
- GET /v1/payment_links
- Your links, with how many payments each has taken.
- DELETE /v1/payment_links/:id
- Turns a link off.
- GET /v1/cards
- Cards your customers kept on file.
- POST /v1/cards/:id/charge
- Takes a payment on a saved card, without the customer present. Secret key only.
- DELETE /v1/cards/:id
- Deletes a saved card at the processor.
- GET /v1/events
- Your events, newest first, in the shape your webhooks receive. Filter by type and since; page with limit and cursor.
- GET /v1/events/:id
- One event.
- GET /v1/account
- Your account: status, plan, this month's use of the monthly limit and whether live payments are open.
Every endpoint, with its parameters, its errors and a request in curl, Node.js and PHP, is in the API reference. Tools can read the same thing as an OpenAPI 3.1 document.
Create a checkout
curl https://api.payments.lk/v1/checkouts \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: order-8891" \
-H "Content-Type: application/json" \
-d '{
"amountCents": 350000,
"description": "Two kottu and a milk tea",
"reference": "order-8891",
"successUrl": "https://yourshop.lk/thanks",
"cancelUrl": "https://yourshop.lk/cart"
}'Send your customer to the url in the answer. When they have paid you receive payment.succeeded; the successUrl is only a way back for their browser, so fulfil the order from the webhook, not from the redirect. The Node.js and PHP SDKs do both for you.
Verify a webhook
import { createHmac, timingSafeEqual } from "node:crypto";
// header: the Payments-Signature header, "t=1726400000,v1=<hex>". While a rotated
// signing secret is kept it carries a v1 for each secret: accept any match.
// body: the raw request body, exactly as received
export function verify(secret, header, body, toleranceSeconds = 300) {
let t;
const signatures = [];
for (const part of String(header ?? "").split(",")) {
const [key, value] = part.trim().split("=", 2);
if (key === "t" && /^[0-9]{1,12}$/.test(value ?? "")) t = Number(value);
if (key === "v1" && /^[0-9a-f]{64}$/.test(value ?? "")) signatures.push(value);
}
if (t === undefined || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(t + "." + body).digest();
return signatures.some((s) => timingSafeEqual(expected, Buffer.from(s, "hex")));
}Sandbox
A sandbox checkout is a real checkout on Payable’s sandbox: the customer lands on Payable’s payment page, pays with one of these test cards, and the outcome comes back as a signed notification and webhook, exactly as it will live. Use CVV 100 (1000 for American Express); the expiry date decides the answer, and on the 3D Secure page choose an authentication result. No money moves. Sandbox checkouts start once you have submitted your application, when Payable’s sandbox has a test merchant for you.
Follow the developer guide, sandbox first- 5123 4500 0000 0008
- Mastercard
- 2223 0000 0000 0007
- Mastercard
- 4508 7500 1574 1019
- Visa
- 3718 812455 60002
- American Express, CVV 1000
- 3600 0000 0000 0123
- Diners Club
- 6445 6445 6445 6460
- Discover
- Expiry 01/39
- Approved
- Expiry 05/39
- Declined
- Expiry 04/27
- Expired card
Webhooks
Signed over the exact bytes sent, with the time inside the signature, and retried at 1, 5 and 30 minutes, then 2, 6 and 12 hours. Add an endpoint, or point events at the test inbox on your dashboard before you have a server.
- payment.succeeded
- The card was authorised. The payment object carries the amount, the fee and the card's last four digits.
- payment.failed
- The card was declined or the payment was not completed.
- checkout.expired
- Nobody paid the checkout before it expired, 30 minutes after it was made.
- card.saved
- The customer kept their card on file. Charge it later with POST /v1/cards/:id/charge.
- card.save_failed
- The payment succeeded but the card was not kept, so no card.saved follows. The payment's cardSave is failed; ask the customer to save the card again.
- refund.succeeded
- The money is on its way back to the customer’s card.
- refund.failed
- The refund could not be made. The refund object says why.
- test.ping
- Sent only when you press Send test event on an endpoint's page in the dashboard, to that endpoint alone. It carries no payment; answer 2xx and do nothing else.