Skip to main content
There are ten payout.* events. Seven follow a payout from the moment it’s sent until the money is final. Three tell you the money didn’t arrive and why. Webhooks lists when each one is sent.
Agents: fetch this page as plain markdown at https://docs.usecleff.com/api-reference/webhook-events-payout.md.
Disbursed, delivered and settled mean different things.
  • Disbursed: the money has been sent. How soon the Beneficiary sees it depends on the rail: within seconds on an instant rail such as RTP, FedNow or a card, and up to a few business days on ACH.
  • Delivered: the money is in the Beneficiary’s account. Use this event to tell someone their money has arrived. It’s sent again if the delivery time is later corrected.
  • Settled: the payment can no longer be reversed, so the money is final.
Rejected, failed and returned all mean the money didn’t arrive.
  • Rejected: an approval decision refused the payout before it was sent. There’s no failure_code. The event carries the decision’s reasons instead.
  • Failed: the money never left Cleff. Either the payout was refused, or Cleff couldn’t confirm what happened to it.
  • Returned: the money was sent and the receiving bank sent it back. prior_state tells you whether the payout was disbursed or already settled. A return after settled reverses a payment you were told was final, so reconcile it.

Content

Every payout.* event has these fields: Some events add fields: The status in an event’s data names the step the event reports. It is not the payout’s own status, which has its own values: payout.disbursed carries status: "disbursed" while the payout reads in_transit. Read the payout for its status. rail_ref is null on payout.rejected, and on payout.failed when the payout failed before it was sent. It’s often null on payout.canceled too. Events don’t carry provider or provider_payout_id. GET /v1/payouts/{id} returns both: provider names who carried the payout for Cleff, and provider_payout_id is their reference for it. Treat both as opaque. Quote them to Cleff support, but don’t build logic on them, because their values can change without notice. Example:

Why a payout failed

payout.failed and payout.returned include four fields that explain what happened: To replace a payout, create a new one with POST /v1/payouts and set replaces_payout_id to the failed payout’s ID. Check replaceable first. indeterminate means Cleff doesn’t know the outcome. The money may already have moved. When the rail gives a reason code, it’s in failure_detail, in that rail’s own vocabulary. For example, a closed account is reported as account_closed whether the reason was an ACH R02, an RTP or FedNow AC04, or a card network’s 41. The same code can mean different things on different rails, so use failure_code in your logic, not failure_detail. If there’s no code, or Cleff doesn’t recognise it, the failure is indeterminate. Some failures block future payouts. After account_frozen, the Beneficiary is suspended and new payouts to them fail with BENEFICIARY_SUSPENDED. After account_closed, account_invalid or account_unauthorized, new payouts to that destination fail with DESTINATION_REPLACEMENT_REQUIRED until the Beneficiary gives a new one. Other codes don’t block anything. replaceable tells you whether it’s safe to create a replacement payout. It’s false when the failure is indeterminate, because the original payout may still have gone through, and false once a replacement already exists. It doesn’t account for a suspended Beneficiary: after account_frozen it can be true, but the replacement is refused with BENEFICIARY_SUSPENDED. The same four fields are on GET /v1/payouts/{id}, so you can still read a failure you missed.

Why a payout was rejected

payout.rejected carries the decision’s reasons. It has no failure_code or fault, because the payout was never sent. Each Reason has a code: MANUAL_REJECTION, BENEFICIARY_NOT_COMPLIANT, RULE_AMOUNT_OVER_LIMIT, RULE_NOT_APPLICABLE, AGENT_REJECTED or CUSTOM. Any message or details you sent with a decision aren’t included in the webhook. GET /v1/payouts/{id} returns your decisions in full. replaceable is always false on payout.rejected. To try again, create a new payout.