POST /api/bankroll/webhook.
It carries one family of events: what became of the
managed references your server
minted, pay-ins and payouts alike. Deliveries are signed and retried until
your route answers 2xx. Bankroll never judges a transaction: it reports the
signature, and your server reads the transaction.
Setup
Your exact origin must be Bankroll Verified. Verification creates your endpoint and hands you its secret with the signed manifest; set it asBANKROLL_WEBHOOK_SECRET (builder apps have it already).
referenceWebhook returns a plain (request: Request) => Promise<Response>
handler: a Next.js route handler as it stands, and usable anywhere else that
takes one. Pass secret to verify with something other than the environment
variable.
Events
Bankroll delivers one event per reference.meta is what you gave createManagedReference, verbatim: it is how you
find the entry again. A failed transaction moves nothing and is not an event;
the reference stays open until something lands or the window ends.
Types are exported from @joinbankroll/sdk/webhooks:
What the route does
- Reads the raw body and verifies the signature against the secret. A delivery that does not verify, or carries no signature, is refused with 401 before any handler runs; a body over 64 KiB with 413; an event the route cannot read with 400.
- Hands a
reference.confirmedtoonConfirmedand areference.expiredtoonExpired, then answers 200. - Lets a handler’s throw propagate. The route answers 500 and Bankroll delivers again later, so never swallow a failure to record what you learned.
Handling a confirmed reference
A reference is public once it lands, so anyone can attach it to a transaction of their own.reference.confirmed is a candidate, not a receipt: always read
the transaction before releasing anything. For a pay-in,
checkCharge reads the charge and
checks it against what the entry was sold for. For a payout your server built
and sent itself, the signature the send answered is the proof: mark the
payout paid when Bankroll reports that one, and take Bankroll’s word only
when the send’s answer was lost.
checkCharge throws ChargeMismatchError with the field that differed;
treat a mismatch as not paid.
On reference.expired, release whatever waited on a pay-in, or treat the
payout as never sent. The window is yours to size when you mint the
reference: nothing lands after a charge’s window plus its blockhash lifetime,
and a Privy-signed payout must not be rebuilt inside its 24 hour replay
window.
Under the mock
WithBANKROLL_MOCK=1 outside production nothing reaches Bankroll. The
reference is minted locally, the mock host’s charge() and a mock payout
deliver reference.confirmed to your route themselves, unsigned, and the
window’s end delivers reference.expired. The route accepts an unsigned
delivery only under the mock. npm run check runs the whole loop.