If you already pay people from your own system (a bank's payout API, a payroll tool, an in-app balance), a referral program can hand each payout to it. Choose Your own system as the program's provider on the Payouts tab and pick one of your webhook endpoints. When you press Send, BoltClaw asks your system for each payout; your system pays it and says what happened. BoltClaw never holds the money.
1. The request
Each payout in a batch you send arrives as referral.payout.requested, signed like every
BoltClaw webhook (Webhooks), at the one endpoint the program chose.
Ticking the event on any other endpoint receives nothing.
{
"event": "referral.payout.requested",
"occurred_at": "2026-11-01T06:00:12Z",
"program_id": "7c1b5f0e-2a7d-4f53-9a8e-0d4b3c2e1f10",
"payout_id": "0f1e2d3c-4b5a-4978-8796-a5b4c3d2e1f0",
"partner_id": "3a9c4e21-8b6d-4c1f-a0e2-5d7f9b1c3e48",
"batch_id": "c2d4e6f8-1a3b-4c5d-8e9f-0a1b2c3d4e5f",
"amount_minor": 12000,
"currency": "USD",
"reference": "bc-0f1e2d3c4b5a",
"method": "paypal",
"external_id": null
}
external_id is your own id for one of your own users (a referrer in your product), or
null for an affiliate. method is the kind of payout details the promoter saved, never the
details: webhook bodies sit in your delivery log, which more people may read than should
see a bank account.
2. Where to send it
Your system reads the destination with its own key, which holds referrals:payouts
(make it in Settings, Developers; an AI assistant's key can never hold it):
curl https://api.boltclaw.ai/v1/referrals/payouts/0f1e2d3c-4b5a-4978-8796-a5b4c3d2e1f0/details \
-H "Authorization: Bearer $BOLTCLAW_PAYOUTS_KEY"
{
"payout_id": "0f1e2d3c-4b5a-4978-8796-a5b4c3d2e1f0",
"amount_minor": 12000,
"currency": "USD",
"reference": "bc-0f1e2d3c4b5a",
"method": "paypal",
"details": { "email": "jane@example.com" },
"external_id": null
}
It answers only while the payout waits to be paid, and 409 while the promoter's payout
details changed in the last 48 hours or they locked their payouts. Every read is recorded in
the activity log, as a payout export is.
3. The result
Pay it, then say what happened. The same answer again changes nothing.
curl -X POST https://api.boltclaw.ai/v1/referrals/payouts/0f1e2d3c-4b5a-4978-8796-a5b4c3d2e1f0/result \
-H "Authorization: Bearer $BOLTCLAW_PAYOUTS_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "paid", "reference": "TXN-99812"}'
status |
Also send | What happens |
|---|---|---|
paid |
reference, your own payment id (optional) |
The commissions are paid; the promoter is emailed; referral.payout.paid |
failed |
reason |
The commissions go back to Ready to pay; the promoter is emailed what to fix |
returned |
reason |
The same, for a payment that came back |
reason is one of invalid_destination, recipient_not_ready, insufficient_funds,
returned or other. A payout already settled differently answers 409.
A worked example
A small Node handler, using the SDK's webhook check (@boltclaw/referrals):
import { verifyWebhook } from "@boltclaw/referrals/server";
export async function POST(req) {
const raw = await req.text();
const event = await verifyWebhook(raw, req.headers, process.env.BOLTCLAW_WEBHOOK_SECRET);
if (event.event !== "referral.payout.requested") return new Response(null, { status: 204 });
const api = "https://api.boltclaw.ai/v1/referrals/payouts/" + event.payout_id;
const auth = { Authorization: `Bearer ${process.env.BOLTCLAW_PAYOUTS_KEY}` };
const where = await (await fetch(`${api}/details`, { headers: auth })).json();
const paid = await myPayoutProvider.send({
to: where.details,
amountMinor: where.amount_minor,
currency: where.currency,
idempotencyKey: event.payout_id, // so a retried webhook never pays twice
});
await fetch(`${api}/result`, {
method: "POST",
headers: { ...auth, "Content-Type": "application/json" },
body: JSON.stringify(paid.ok ? { status: "paid", reference: paid.id } : { status: "failed", reason: "other" }),
});
return new Response(null, { status: 204 });
}
Use the payout id as your own idempotency key: a webhook can arrive more than once.
When the endpoint is off
If the chosen endpoint is switched off or deleted, nothing is queued: the payout stays sent and waiting, the connection shows the problem, and you can mark it paid or failed by hand on the Payouts tab once you know what happened.