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.
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.
// 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 yoursk_live_…key. - Switch
success_urlandcancel_urlto 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.