beneficiary.* events report that a Beneficiary — or one of their bank accounts —
finished registering. They share the
envelope and the
ordering contract every Cleff
webhook uses; subscribe to them by name in enabled_events.
Beneficiary events carry no Payout context — no payout_id, no amount, no rail_ref.
They are scoped to the Beneficiary, and order per Beneficiary by the same
(occurred_at, sequence) rule the Payout events use.
The two are deliberately separate concerns, split by ADR-0030: identity is a
Beneficiary-level fact, while a bank account is a per-account fact and one Beneficiary
can register several accounts over their life.
beneficiary.details_submitted
Fires when a Beneficiary completes an identity collection link — the Cleff-hosted
form behind a full_onboarding invite. Beneficiaries do not authenticate to Cleff and
have no portal, so this webhook is the only way a Business learns that a Beneficiary
finished: subscribe to it rather than polling GET /v1/beneficiaries/{id}.
Identity only. ADR-0030 narrowed this event to identity collection and moved bank
details to beneficiary.bank_account.registered. A bank_details-scoped link fires only
beneficiary.bank_account.registered; a full_onboarding submit fires both, because an
account was registered and identity was completed.
Re-fires once per completed identity submission. A fresh
full_onboarding link issued to
an already-onboarded Beneficiary re-collects their identity and fires the event again, so
treat it as an update rather than a first-time signal.
beneficiary.bank_account.registered
Fires on a bank-account registration through any channel: a Business adding payment
details from the dashboard, a Beneficiary submitting through a Cleff-hosted collection
link, or a direct API registration. Channel-agnostic is the point — the subscriber learns
that a payable destination now exists, never how it was sourced. How the account was
collected stays internal and is not on the wire. Bulk import is the one deliberate
exception, noted below.
Fires once per registered account, so a Beneficiary with several accounts produces
several events. Editing an existing account does not re-fire it.
POST /v1/beneficiaries/import deliberately fires no beneficiary.bank_account.registered
for its rows. The response already carries the complete result for every row synchronously, so the
caller holds everything the webhook would have delivered. A consumer that builds its state purely
from the event stream will not see imported accounts — read them from results instead. See
Importing a verified base.