Skip to content

Webhooks for platforms

Your connected accounts’ events go to a Connect endpoint, each naming the account it happened on. Your own events (your fees, transfers and accounts’ losses) go to an ordinary endpoint.

Your connected accountsacct_…Your platformyour own accountConnect endpointconnect: trueYour endpointconnect: falseaccount.*, payment.*, … + accountapplication_fee.*, transfer.*, …

Create it with "connect": true (fixed once made). It receives only your connected accounts’ events, none of your own.

Terminal window
curl https://api.tuppence.ai/v1/webhook_endpoints \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/tuppence/connect", "connect": true,
"enabled_events": ["account.updated", "payment.succeeded"]}'

An event it receives:

{
"id": "evt_…",
"object": "event",
"account": "acct_1…",
"type": "payment.succeeded",
"data": { "object": { "id": "pay_…", "object": "payment", "amount": 4500 } }
}

Like every endpoint, it has its own signing secret, retries and three-day switch-off (Webhooks).

Verify the signature, skip ids you’ve handled, then dispatch on account and type.

app.post("/tuppence/connect", express.raw({ type: "application/json" }), async (req, res) => {
const event = tuppence.webhooks.constructEvent(
req.body,
req.headers["tuppence-signature"],
process.env.TUPPENCE_CONNECT_WEBHOOK_SECRET,
);
if (await alreadyHandled(event.id)) return res.sendStatus(200); // a retry of one you handled
const business = await businessForTuppenceAccount(event.account);
if (event.type === "account.updated") await business.onboardingChanged(event.data.object);
if (event.type === "payment.succeeded") await business.paid(event.data.object);
await markHandled(event.id);
res.sendStatus(200);
});
FactSo
A failed delivery is retried with the same event idRecord handled ids; answer 200 to a repeat. Ids are unique across all your accounts
Events can arrive out of orderRead state from data.object (or retrieve it, as the account), not from arrival order
One happening can be two eventsA transfer is transfer.created on your own stream and on the account’s, with different ids

On your Connect endpoint, for each connected account:

EventWhen
account.created, account.closedYou made or closed the account
account.updated, account.requirements_due, account.charges_enabled, account.payouts_enabledVerification moved it: onboarding completed, more due by a deadline, payments or payouts on. Each carries the Account object
account.identity_updatedAn identity check on its person was submitted, verified or failed
capability.updatedcard_payments, transfers or card_issuing changed status
person.created, person.updated, person.deletedIts people, by id and role, never a name
account.external_account.created, .updated, .deletedIts bank accounts, never their numbers
transfer.created, transfer.reversed, transfer.failedMoney you sent it, or took back
cashback.statement_ready, cashback.paidIts month’s cashback statement was made, then paid: your net into your Connect balance, its share_net to it
card_dispute.*Its card disputes
Everything else it doespayment.*, refund.*, payout.*, booking.*: the events your own account would have

On your own endpoint:

EventWhen
application_fee.created, application_fee.refundedYour fee on an account’s payment, and any you gave back
transfer.created, transfer.reversed, transfer.failedYour transfers, from your side
account.negative_balanceAn account was three days below zero, and what covered it (losses)

Alerts about a connected account come to you, named for it.

ToCall
Read an account’s eventsGET /v1/events with Tuppence-Account: acct_…, or GET /v1/events?account=acct_… with your own key
Resend one to your Connect endpointPOST /v1/events/{id}/resend with {"webhook_endpoint": "we_…", "account": "acct_…"}

Docs assistant

Open as a page
Loading the assistant…