API Reference
Calmony Pay is a Stripe-compatible payment processing API. All endpoints follow REST conventions and return JSON. Use your API key to authenticate every request.
Quick Start
Get up and running in minutes. Create your first payment intent with a single API call.
1. Get your API key from the dashboard
# Your test key starts with sk_test_
# Your live key starts with sk_live_
export CALMONY_API_KEY=sk_test_your_key_here2. Create a customer
curl https://api.calmonypay.com/v1/customers \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","name":"Alice Smith"}'3. Create and confirm a payment intent
curl https://api.calmonypay.com/v1/payment_intents \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"amount": 2500,
"currency": "gbp",
"customerId": "cus_01HXYZ",
"paymentMethodId": "pm_01HXYZ",
"confirm": true
}'Authentication
Calmony Pay uses API keys to authenticate requests. You can view and manage your API keys in the Dashboard. Test mode keys start with sk_test_ and live keys start with sk_live_.
Keep your API key secret
Never share your secret API key in publicly accessible areas such as GitHub, client-side code, or emails.
Bearer Token
Pass your API key as a Bearer token in the Authorization header on every request.
curl https://api.calmonypay.com/v1/customers \
-H "Authorization: Bearer sk_live_your_key_here"Rate Limiting
The API allows up to 100 requests per second. Exceeding this limit returns a 429 Too Many Requests response.
Idempotency Keys
Add an Idempotency-Key header to POST requests to prevent duplicate operations. Keys expire after 24 hours.
curl https://api.calmonypay.com/v1/payment_intents \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: a4e2c8f0-1234-5678-abcd-ef0123456789" \
-H "Content-Type: application/json" \
-d '{"amount":2500,"currency":"gbp"}'TypeScript SDK
The official Calmony Pay TypeScript SDK provides a type-safe interface for all API operations.
Installation
npm install calmony-payInitialisation
import { CalmonyPay } from "calmony-pay";
const client = new CalmonyPay({
apiKey: process.env.CALMONY_API_KEY!, // sk_live_... or sk_test_...
});Full SDK Example
import { CalmonyPay } from "calmony-pay";
const pay = new CalmonyPay({ apiKey: process.env.CALMONY_API_KEY! });
// Create a customer
const customer = await pay.customers.create({
email: "user@example.com",
name: "Alice Smith",
});
// Tokenise a payment method
const paymentMethod = await pay.paymentMethods.create({
customerId: customer.id,
card: { number: "4111111111111111", expMonth: 12, expYear: 2026, cvc: "123" },
});
// Attach it to the customer
await pay.paymentMethods.attach(paymentMethod.id, { customerId: customer.id });
// Create and confirm a payment intent
const intent = await pay.paymentIntents.create({
amount: 2500, // in pence
currency: "gbp",
customerId: customer.id,
paymentMethodId: paymentMethod.id,
confirm: true,
});
console.log(intent.status); // "succeeded"
// Verify a webhook signature
const event = CalmonyPay.webhooks.verifySignature(
rawBody,
request.headers.get("Calmony-Signature")!,
process.env.WEBHOOK_SECRET!
);SDK Methods Reference
| Namespace | Methods |
|---|---|
| customers | create · retrieve · update · delete · list |
| paymentMethods | create · retrieve · list · attach · detach |
| paymentIntents | create · confirm · retrieve · list |
| subscriptions | create · update · cancel · retrieve · list |
| invoices | create · retrieve · list · downloadPdf |
| checkout.sessions | create · retrieve |
| CalmonyPay.webhooks | verifySignature(payload, signature, secret) |
Customers
Customer objects represent your users. Attach payment methods and subscriptions to a customer. Customer IDs are prefixed with cus_.
Payment Methods
Payment methods represent card details tokenised via Cardstream. They are prefixed with pm_ and can be reused across multiple payments.
Payment Intents
A Payment Intent tracks the lifecycle of a single payment. Amounts are always in the smallest currency unit (pence for GBP). Payment Intent IDs are prefixed with pi_.
Subscriptions
Subscriptions enable recurring billing on a monthly or annual interval. Subscription IDs are prefixed with sub_.
Invoices
Invoices are generated automatically for subscriptions and can be created manually. Invoice IDs are prefixed with inv_.
Checkout Sessions
Hosted Checkout Sessions redirect customers to a Cardstream-hosted payment page. No card data touches your server. Session IDs are prefixed with cs_.
Webhooks
Webhooks notify your server of asynchronous events. Each delivery is signed with HMAC-SHA256 using your webhook secret. Retries use exponential backoff.
Register a Webhook Endpoint
curl https://api.calmonypay.com/v1/webhook_endpoints \
-X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/calmony",
"enabledEvents": [
"payment_intent.succeeded",
"payment_intent.failed",
"subscription.created",
"invoice.paid"
]
}'Verify the Signature
Always verify the Calmony-Signature header before processing events.
// app/api/webhooks/route.ts
import { NextRequest, NextResponse } from "next/server";
import { CalmonyPay } from "calmony-pay";
export async function POST(req: NextRequest) {
const rawBody = await req.text();
const signature = req.headers.get("Calmony-Signature") ?? "";
const secret = process.env.CALMONY_WEBHOOK_SECRET!;
let event;
try {
event = CalmonyPay.webhooks.verifySignature(rawBody, signature, secret);
} catch (err) {
return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
}
switch (event.type) {
case "payment_intent.succeeded":
console.log("Payment succeeded:", event.data.object.id);
break;
case "payment_intent.failed":
console.log("Payment failed:", event.data.object.id);
break;
case "subscription.created":
console.log("Subscription created:", event.data.object.id);
break;
case "invoice.paid":
console.log("Invoice paid:", event.data.object.id);
break;
}
return NextResponse.json({ received: true });
}Event Catalogue
| Event | Description |
|---|---|
| payment_intent.succeeded | A payment was successfully captured |
| payment_intent.failed | A payment attempt failed |
| payment_method.attached | A card was attached to a customer |
| customer.created | A new customer was created |
| customer.updated | A customer's details were updated |
| customer.deleted | A customer was deleted |
| subscription.created | A new subscription was started |
| subscription.updated | A subscription's details changed |
| subscription.deleted | A subscription was cancelled |
| invoice.created | A new invoice was generated |
| invoice.paid | An invoice was marked as paid |
| invoice.payment_failed | An invoice payment attempt failed |
| checkout.session.completed | A hosted checkout was completed |
Webhook Payload Shape
{
"id": "evt_01HXYZABCDEF",
"type": "payment_intent.succeeded",
"created": 1705312200,
"data": {
"object": {
"id": "pi_01HXYZABCDEF",
"amount": 2500,
"currency": "gbp",
"status": "succeeded"
}
}
}Errors
Calmony Pay uses conventional HTTP response codes. All errors return a JSON body with an error object.
Error Response Format
{
"error": {
"type": "invalid_request_error",
"message": "No such customer: cus_01HXYZ",
"param": "customerId"
}
}HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request — invalid parameters |
| 401 | Unauthorized — missing or invalid API key |
| 402 | Payment Required — card declined |
| 404 | Not Found — resource doesn't exist |
| 409 | Conflict — idempotency key reuse with different params |
| 422 | Unprocessable Entity — validation failed |
| 429 | Too Many Requests — rate limit exceeded |
| 500 | Internal Server Error |
Error Types
| Type | Description |
|---|---|
| api_error | A server-side error occurred. Safe to retry. |
| authentication_error | Invalid or missing API key. |
| card_error | The card was declined or invalid. |
| idempotency_error | Idempotency key was reused with different params. |
| invalid_request_error | The request was malformed or missing a required field. |
| rate_limit_error | Too many requests. Back off and retry. |