Skip to main content
Two 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.