Quickstart

Five minutes from zero to a real, signed charge. This guide uses test-mode keys so you can run it safely against the live API.

1. Create an account and get your keys

Sign up, verify your business email, and copy your sk_test_… secret key from Dashboard → API keys.

2. Create a checkout session

Call POST /v1/checkout/sessions from your server. The response includes a hosted checkout URL — redirect the customer there.

create-session.sh
curl https://api.trustfabric.ai/v1/checkout/sessions \
  -H "Authorization: Bearer $PAID_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1234" \
  -d '{
    "mode": "payment",
    "currency": "USD",
    "payment_method_types": ["card"],
    "line_items": [{
      "ad_hoc": {"amount": 4999, "currency": "USD", "description": "Pro plan"},
      "quantity": 1
    }],
    "success_url": "https://example.com/thanks",
    "cancel_url": "https://example.com/cancel"
  }'

3. Handle the webhook

When the customer completes payment, PAID delivers a signed payment_intent.captured event to your webhook endpoint. Verify the signature, then grant access or fulfill the order.

webhook.ts
// POST https://your-app.com/api/webhooks/paid
// PAID signs every event with your endpoint's whsec_ secret:
//   Webhook-Signature: t=<unix_ts>,v1=<hmac_sha256_hex over "<ts>.<raw body>">
// Companion headers (Standard Webhooks-style names): Webhook-ID for
// idempotency, Webhook-Timestamp for replay rejection.

import crypto from "node:crypto";

function verifyPaidSignature(payload: string, header: string, secret: string): boolean {
  const [tPart, sPart] = header.split(",");
  const t = (tPart ?? "").replace(/^t=/, "");
  const s = (sPart ?? "").replace(/^v1=/, "");
  const expected = crypto
    .createHmac("sha256", secret)
    .update(t + "." + payload)
    .digest("hex");
  return (
    s.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(s))
  );
}

export async function POST(req: Request) {
  const payload = await req.text();
  const sig = req.headers.get("Webhook-Signature") ?? "";
  if (!verifyPaidSignature(payload, sig, process.env.PAID_WEBHOOK_SECRET!)) {
    return new Response("bad signature", { status: 400 });
  }

  const event = JSON.parse(payload);
  switch (event.type) {
    case "payment_intent.captured":
      await grantEntitlement(event.data.customer_id);
      break;
    case "payment_intent.failed":
      await notifyCustomer(event.data.customer_id, event.data.last_error);
      break;
  }

  return new Response("ok");
}

4. Go live

  • Replace sk_test_… with your sk_live_… key.
  • Switch success_url and cancel_url to production hosts.
  • Register your live webhook URL in Dashboard → Webhooks.
Your test-mode events and live-mode events are completely isolated. You can run both at the same time without contamination.