Getting started
Official SDKs
PHP and JavaScript/TypeScript libraries for creating payment requests and verifying webhooks.
The SDKs wrap the same HTTP API documented here: they create, retrieve and list payment requests, read your payment methods, send an Idempotency-Key when you pass one, turn error responses into exceptions, and verify webhook signatures so you never hand-roll the HMAC. Responses are the API's own JSON, field for field. Both have no dependencies and live in one open-source repository on GitHub.
PHP
PHP 8.1+ with the curl extension, on plain PHP, Laravel or WordPress alike. Published on Packagist.
composer require kasera/kasera-pay
Create a payment request
use Kasera\Pay\ApiException;
use Kasera\Pay\Client;
$kasera = new Client(getenv('KASERA_API_KEY')); // kp_test_... or kp_live_...
try {
$tx = $kasera->createTransaction([
'amount' => 150000, // whole rupiah
'external_id' => 'order-1001',
'checkout' => new stdClass(), // hosted Kasera Pay Checkout
], 'order-1001'); // Idempotency-Key
} catch (ApiException $e) {
// $e->status, $e->errorCode (e.g. validation_failed), $e->fields, $e->requestId
throw $e;
}
header('Location: ' . $tx['checkout_url']);Verify a webhook
use Kasera\Pay\SignatureException;
use Kasera\Pay\Webhook;
try {
$event = Webhook::constructEvent(
file_get_contents('php://input'), // the raw body
$_SERVER['HTTP_KASERA_SIGNATURE_V1'] ?? '',
getenv('KASERA_WEBHOOK_SECRET'),
);
} catch (SignatureException) {
http_response_code(400);
exit;
}
if ($event['type'] === 'payment.paid') {
// mark $event['data']['external_id'] paid; dedupe on $event['id']
}JavaScript / TypeScript
Node 20.19+, Deno, Bun and edge runtimes, with TypeScript types generated from the API spec. Published on npm.
npm install kasera-pay
Create a payment request
import { KaseraPay, KaseraPayError } from "kasera-pay";
const kasera = new KaseraPay(process.env.KASERA_API_KEY!); // kp_test_... or kp_live_...
try {
const tx = await kasera.createTransaction(
{ amount: 150000, external_id: "order-1001", checkout: {} },
{ idempotencyKey: "order-1001" },
);
// redirect the buyer to tx.checkout_url
} catch (e) {
if (e instanceof KaseraPayError) {
// e.status, e.code (e.g. validation_failed), e.fields, e.requestId
}
throw e;
}Verify a webhook
import { constructWebhookEvent, SignatureError } from "kasera-pay";
// e.g. a Next.js route handler
export async function POST(req: Request) {
try {
const event = await constructWebhookEvent(
await req.text(), // the raw body
req.headers.get("kasera-signature-v1") ?? "",
process.env.KASERA_WEBHOOK_SECRET!,
);
if (event.type === "payment.paid") {
// mark event.data.external_id paid; dedupe on event.id
}
return new Response("ok");
} catch (e) {
if (e instanceof SignatureError) return new Response("bad signature", { status: 400 });
throw e;
}
}Always pass an idempotency key
Your order id is a good key. It is the only thing that stops a retried create — a double click, a timeout, a queue redelivery — from becoming a second payment request: the same key with the same body returns the original, and the same key with a different body is refused with 409 idempotency_conflict. external_id alone does not deduplicate.
Test first
With a kp_test_ key every call works end to end and no real money moves; pay test requests with the simulate button on the checkout page. See Test mode. Going live is swapping the key.
Using another language? The API is plain HTTPS and JSON, and its OpenAPI spec is served at /v1/openapi.json for generating a client.