Caravel Pay Docs

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

Connect once
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#

Create a checkout and redirect
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#

Express
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#

CallEndpoint
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:

  • CaravelApiError has code, status, param, retryAfter and retryable.
  • CaravelNetworkError means no response arrived.
  • CaravelUnexpectedResponseError means a malformed 2xx answer.
  • CaravelSignatureError has reason and status.

From CommonJS, use await import("caravel-pay"). On Node 20.19+ and 22.12+, require("caravel-pay") also works.