SDKs
Node.js SDK
caravel-pay is ESM with TypeScript types and has zero dependencies: it uses Node's built-in fetch and node:crypto. It signs every request, verifies webhooks in constant time and checks responses before you use them. Node 18.17 or newer.
Install#
Install the package from its tarball. npm records the address in package.json, so your deploys fetch the same file.
npm install https://caravelpay.app/download/caravel-pay-1.0.0.tgz
Or download caravel-pay-1.0.0.tgz and run npm install ./caravel-pay-1.0.0.tgz.
Available at launch. The downloads on this page open when Caravel Pay launches.
Get API keys#
Run once and keep the result in your server's environment. Every call creates a new merchant. See Get your API keys.
import { connect } from "caravel-pay";
const keys = await connect({
site_url: "https://shop.example",
payout_address: "0x52908400098527886E0F7030069857D2E4169EE7",
});
// { merchant_id: "mch_…", secret: "…", status: "active", fee_rate: "2%" }
Create a checkout#
import { Caravel, normalizeAmount } from "caravel-pay";
const caravel = new Caravel({
merchantId: process.env.CARAVEL_MERCHANT_ID,
secret: process.env.CARAVEL_SECRET,
maxRetries: 2,
});
const checkout = await caravel.createCheckout({
order_id: "1042",
amount: normalizeAmount(order.total), // "136.41"
return_url: "https://shop.example/thanks",
cancel_url: "https://shop.example/cart",
callback_url: "https://shop.example/webhooks/caravel",
});
res.redirect(303, checkout.checkout_url);
Handle the webhook#
import express from "express";
import { CaravelSignatureError, isPaid, txHash, verifyWebhook } from "caravel-pay";
// express.raw keeps the exact bytes that were signed
app.post("/webhooks/caravel", express.raw({ type: "application/json" }), async (req, res) => {
let event;
try {
event = verifyWebhook({
body: req.body,
headers: req.headers,
secret: process.env.CARAVEL_SECRET, // or { [merchantId]: secret } while old keys are in use
callbackUrl: "https://shop.example/webhooks/caravel",
});
} catch (error) {
if (error instanceof CaravelSignatureError) return res.sendStatus(error.status);
throw error;
}
if (await alreadyHandled(event.id)) return res.sendStatus(200);
// Re-read the checkout before changing the order.
const checkout = await caravel.getCheckout(event.data.id);
if (isPaid(checkout)) await markPaid(checkout.order_id, txHash(checkout));
await remember(event.id);
res.sendStatus(200);
});
headers can be a plain object or a Headers instance. body can be a string, Buffer or Uint8Array, but never parsed JSON.
Reference#
| Call | Endpoint |
|---|---|
connect(params) | POST /v1/connect, unsigned, never retried |
caravel.createCheckout(params, { idempotencyKey }) | POST /v1/checkouts |
caravel.getCheckout(id) | GET /v1/checkouts/{id} |
caravel.listCheckouts({ limit, cursor }), for await (… of caravel.allCheckouts()) | GET /v1/checkouts |
caravel.getMerchant() | GET /v1/merchant |
caravel.createPaymentLink(params), Caravel.createPaymentLinkWithoutKeys(params) | POST /v1/payment-links |
caravel.getPaymentLink(id), deactivatePaymentLink(id) | GET /v1/payment-links/{id}, POST …/deactivate |
verifyWebhook({ body, headers, secret, callbackUrl }) | Verifies a webhook and returns the event |
Errors:
CaravelApiErrorhascode,status,param,retryAfterandretryable.CaravelNetworkErrormeans no response arrived.CaravelUnexpectedResponseErrormeans a malformed 2xx answer.CaravelSignatureErrorhasreasonandstatus.
From CommonJS, use await import("caravel-pay"). On Node 20.19+ and 22.12+, require("caravel-pay") also works.