Client SDKs
Typed clients with idempotency, retries, and webhook verification built in.
Every kepa SDK is built on top of the same REST and WebSocket surface you'd hit with raw HTTP, but it ships with the production-grade defaults baked in: automatic Idempotency-Key generation, retry with exponential backoff, webhook signature verification, and typed request/response models generated from the OpenAPI spec.
Available clients
@kepa/sdkNode 18+, Deno, Bun, Cloudflare WorkersGAkepaPython 3.10+ (sync + async)GAco.kepapay:sdkAndroid, JVM (bundled with Local Embedded kernel)BETAKepaSDKiOS 16+, macOS 13+COMING SOONkepapay-goGo 1.22+COMING SOONatlas_softposRuby 3.2+COMING SOONKepa.SDK.NET 8+ (many POS systems)COMING SOONSDKs marked Coming Soon are in development. Request early access from your account manager or email [email protected].
TypeScript
npm install @kepa/sdk
# or
pnpm add @kepa/sdk
# or
yarn add @kepa/sdkThe TypeScript client is fully typed end-to-end. Every request and response comes straight from the OpenAPI spec, and the client handles idempotency, retries, and request IDs for you.
import { Kepa } from "@kepa/sdk";
const kepa = new Kepa({
apiKey: process.env.ATLAS_API_KEY!,
baseUrl: process.env.BASE_URL, // defaults to https://api.kepapay.co
apiVersion: "2026-04-09", // pin your integration
timeout: 90_000, // 90s — orchestrated sales block
maxRetries: 3, // automatic retry with same Idempotency-Key
});
// Orchestrated sale. Idempotency-Key is generated for you and reused across retries.
const result = await atlas.transactions.sale({
amount: 2500,
currency: "NZD",
referenceId: "POS-INV-20260409-001",
metadata: { cashier: "Jane", register: "REG-02" },
});
if (result.status === "APPROVED") {
console.log("Auth code:", result.authorizationCode);
}Granular mode feels the same — just a second call:
// Granular mode — POS routes the auth itself.
const initiated = await atlas.granular.initiate({
amount: 5000,
currency: "NZD",
});
// Route to your own acquirer...
const issuerResponse = await myAcquirer.authorize(initiated.chipData);
// Hand the issuer response back to the terminal.
const final = await atlas.granular.complete({
transactionId: initiated.transactionId,
responseCode: issuerResponse.code,
authorizationCode: issuerResponse.authCode,
issuerAuthData: issuerResponse.authData,
});Webhook signature verification is a one-liner. The helper throws if the signature is invalid or the timestamp is outside the replay window:
import express from "express";
import { Kepa } from "@kepa/sdk";
const app = express();
app.use(express.raw({ type: "application/json" }));
app.post("/atlas-webhook", (req, res) => {
try {
const event = kepa.webhooks.constructEvent(
req.body,
req.headers["atlas-signature"] as string,
process.env.ATLAS_WEBHOOK_SECRET!,
);
switch (event.type) {
case "transaction.settled":
// ... reconcile in your system
break;
case "dispute.opened":
// ... notify the merchant
break;
}
res.status(200).end();
} catch {
res.status(401).end();
}
});Python
pip install kepa
# or
poetry add kepa
# or
uv add kepaThe Python client targets Python 3.10+ and supports both sync and async usage (atlas_softpos.aio). Same idempotency and retry defaults as TypeScript.
from kepa import Kepa
kepa = Kepa(
api_key=os.environ["ATLAS_API_KEY"],
base_url=os.environ.get("BASE_URL"),
api_version="2026-04-09",
timeout=90,
max_retries=3,
)
# Orchestrated sale with automatic idempotency + retry.
result = atlas.transactions.sale(
amount=2500,
currency="NZD",
reference_id="POS-INV-20260409-001",
metadata={"cashier": "Jane", "register": "REG-02"},
)
if result.status == "APPROVED":
print(f"Auth code: {result.authorization_code}")from flask import Flask, request, abort
from kepa import Kepa, SignatureVerificationError
app = Flask(__name__)
ATLAS_WEBHOOK_SECRET = os.environ["ATLAS_WEBHOOK_SECRET"]
@app.post("/atlas-webhook")
def atlas_webhook():
try:
event = kepa.webhooks.construct_event(
payload=request.get_data(),
signature=request.headers["Kepa-Signature"],
secret=ATLAS_WEBHOOK_SECRET,
)
except SignatureVerificationError:
abort(401)
if event.type == "transaction.settled":
... # reconcile
elif event.type == "dispute.opened":
... # notify merchant
return ("", 200)Kotlin (Local Embedded)
The Kotlin SDK is different from the others — it includes the EMV kernel itself. Use it when you want the terminal to run inside your merchant app process rather than as a separate REST surface. See Connectivity & Flows for when to choose Local Embedded over Local Connected or Cloud.
Design principles
- Zero boilerplate for the common path. If you can call raw HTTP, you can call the SDK. The defaults are production-grade.
- Idempotency and retries on by default. Turn them off explicitly if you need to — never the other way around.
- Generated from the OpenAPI spec. Every SDK is regenerated on every API release. Your types always match the server.
- Typed webhook events. Discriminated unions on event.type so your switch statement is exhaustive at compile time.
- Thin surface. SDKs don't add concepts, routing, or caching. They're a transport, not a framework.