Documentation

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.

REST APIJSONTypeScript SDKWebhooks

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_here

2. 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-pay

Initialisation

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

NamespaceMethods
customerscreate · retrieve · update · delete · list
paymentMethodscreate · retrieve · list · attach · detach
paymentIntentscreate · confirm · retrieve · list
subscriptionscreate · update · cancel · retrieve · list
invoicescreate · retrieve · list · downloadPdf
checkout.sessionscreate · retrieve
CalmonyPay.webhooksverifySignature(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.

Next.js webhook handlertypescript
// 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

EventDescription
payment_intent.succeededA payment was successfully captured
payment_intent.failedA payment attempt failed
payment_method.attachedA card was attached to a customer
customer.createdA new customer was created
customer.updatedA customer's details were updated
customer.deletedA customer was deleted
subscription.createdA new subscription was started
subscription.updatedA subscription's details changed
subscription.deletedA subscription was cancelled
invoice.createdA new invoice was generated
invoice.paidAn invoice was marked as paid
invoice.payment_failedAn invoice payment attempt failed
checkout.session.completedA 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

CodeMeaning
200Success
201Created
400Bad Request — invalid parameters
401Unauthorized — missing or invalid API key
402Payment Required — card declined
404Not Found — resource doesn't exist
409Conflict — idempotency key reuse with different params
422Unprocessable Entity — validation failed
429Too Many Requests — rate limit exceeded
500Internal Server Error

Error Types

TypeDescription
api_errorA server-side error occurred. Safe to retry.
authentication_errorInvalid or missing API key.
card_errorThe card was declined or invalid.
idempotency_errorIdempotency key was reused with different params.
invalid_request_errorThe request was malformed or missing a required field.
rate_limit_errorToo many requests. Back off and retry.