> ## Documentation Index
> Fetch the complete documentation index at: https://docs.killb.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Payout Status Tracking

> Monitor and handle payout disbursement status

## Status Overview

A payout moves through several phases from creation to final settlement. Each phase has three statuses — Pending, Processing, and Completed — giving you granular visibility at every step of the pipeline.

## Status Flow

```mermaid theme={null}
graph TD
    CREATED --> CASH_IN_PENDING
    CASH_IN_PENDING --> CASH_IN_PROCESSING
    CASH_IN_PROCESSING --> CASH_IN_COMPLETED
    CASH_IN_COMPLETED --> KYT_OUT_PENDING
    KYT_OUT_PENDING --> KYT_OUT_PROCESSING
    KYT_OUT_PROCESSING --> KYT_OUT_COMPLETED
    KYT_OUT_COMPLETED --> CASH_OUT_PENDING
    CASH_OUT_PENDING --> CASH_OUT_PROCESSING
    CASH_OUT_PROCESSING --> CASH_OUT_COMPLETED
    CASH_OUT_COMPLETED --> COMPLETED

    KYT_OUT_PROCESSING -.-> REVIEW_NEEDED
    REVIEW_NEEDED -.->|resolved| KYT_OUT_COMPLETED
    REVIEW_NEEDED -.->|blocked| REJECTED

    FAILED --> REFUND_PENDING
    ERROR --> REFUND_PENDING
    REJECTED --> REFUND_PENDING
    REFUND_PENDING --> REFUND_PROCESSING
    REFUND_PROCESSING --> REFUNDED

    CASH_IN_PROCESSING -.-> FAILED
    CASH_IN_PROCESSING -.-> ERROR

    KYT_OUT_PROCESSING -.-> FAILED
    KYT_OUT_PROCESSING -.-> ERROR
    CASH_OUT_PROCESSING -.-> FAILED
    CASH_OUT_PROCESSING -.-> REJECTED
    CASH_OUT_PROCESSING -.-> ERROR

    style COMPLETED fill:#22c55e,color:#fff
    style REJECTED fill:#f97316,color:#fff
    style FAILED fill:#ef4444,color:#fff
    style ERROR fill:#ef4444,color:#fff
    style REFUNDED fill:#6366f1,color:#fff
    style REVIEW_NEEDED fill:#eab308,color:#000
```

<Warning>
  During the **Cash In** phase, `FAILED` and `ERROR` are terminal statuses — no funds were collected, so no refund is issued. For all subsequent phases (KYT Out, Cash Out), `FAILED` and `ERROR` are transient: the payout transitions to the refund flow (`REFUND_PENDING` → `REFUND_PROCESSING` → `REFUNDED`). `REJECTED` also triggers the refund flow since it only occurs after Cash In.
</Warning>

## Status Descriptions

| Status                | Phase       | Description                                                                                                                       | Suggested User Message                        |
| --------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `CREATED`             | Initial     | Payout received and persisted, awaiting queue entry                                                                               | "Payout submitted"                            |
| `CASH_IN_PENDING`     | Cash In     | Incoming fund collection queued                                                                                                   | "Processing payment"                          |
| `CASH_IN_PROCESSING`  | Cash In     | Collecting funds from pre-fund balance                                                                                            | "Collecting funds"                            |
| `CASH_IN_COMPLETED`   | Cash In     | Funds collected successfully                                                                                                      | "Funds collected"                             |
| `KYT_OUT_PENDING`     | KYT Out     | Outgoing transaction queued for compliance check                                                                                  | "Outbound check pending"                      |
| `KYT_OUT_PROCESSING`  | KYT Out     | Compliance review in progress (outgoing)                                                                                          | "Outbound check in progress"                  |
| `KYT_OUT_COMPLETED`   | KYT Out     | Outgoing compliance check passed                                                                                                  | "Ready for transfer"                          |
| `CASH_OUT_PENDING`    | Cash Out    | Disbursement to beneficiary queued                                                                                                | "Transfer queued"                             |
| `CASH_OUT_PROCESSING` | Cash Out    | Funds submitted to payment provider                                                                                               | "Transfer in progress"                        |
| `CASH_OUT_COMPLETED`  | Cash Out    | Payment provider confirmed delivery                                                                                               | "Transfer delivered"                          |
| `COMPLETED`           | Terminal    | Payout fully settled                                                                                                              | "Payment delivered!"                          |
| `REVIEW_NEEDED`       | Review      | Transaction flagged for manual compliance review                                                                                  | "Under review — we'll notify you"             |
| `FAILED`              | Error State | Operation failed. Terminal if during Cash In (no funds collected). Transitions to refund flow if during KYT Out or Cash Out.      | "Transfer failed — check beneficiary details" |
| `REJECTED`            | Error State | Payout blocked by compliance review. Always transitions to refund flow since it only occurs after Cash In.                        | "Transfer rejected — compliance issue"        |
| `ERROR`               | Error State | Internal system error. Terminal if during Cash In (no funds collected). Transitions to refund flow if during KYT Out or Cash Out. | "Internal error — contact support"            |
| `REFUND_PENDING`      | Refund      | Refund to pre-fund balance queued                                                                                                 | "Refund initiated"                            |
| `REFUND_PROCESSING`   | Refund      | Refund in progress                                                                                                                | "Refund in progress"                          |
| `REFUNDED`            | Terminal    | Funds returned to pre-fund balance                                                                                                | "Funds refunded to balance"                   |

## Getting Payout Status

```bash theme={null}
GET /api/v2/payouts/{id}
```

```javascript theme={null}
const getPayoutStatus = async (payoutId) => {
  const response = await fetch(
    `https://sandbox.killb.app/api/v2/payouts/${payoutId}`,
    {
      headers: { 'Authorization': `Bearer ${token}` }
    }
  );

  const payout = await response.json();
  return payout.status;
};
```

## Polling for Status

Use polling as a fallback when webhooks are unavailable:

```javascript theme={null}
const waitForPayout = async (payoutId, timeoutMs = 300000) => {
  const terminalStatuses = ['COMPLETED', 'FAILED', 'REJECTED', 'ERROR', 'REFUNDED'];
  const start = Date.now();

  while (Date.now() - start < timeoutMs) {
    const response = await fetch(
      `https://sandbox.killb.app/api/v2/payouts/${payoutId}`,
      { headers: { 'Authorization': `Bearer ${token}` } }
    );

    const payout = await response.json();
    console.log('Status:', payout.status);

    if (terminalStatuses.includes(payout.status)) {
      return payout;
    }

    // Wait 15 seconds before next check
    await new Promise(resolve => setTimeout(resolve, 15000));
  }

  throw new Error(`Payout ${payoutId} did not settle within timeout`);
};

const result = await waitForPayout('payout-abc123');

if (result.status === 'COMPLETED') {
  console.log('Funds delivered successfully');
} else {
  console.error('Payout ended with status:', result.status);
}
```

<Tip>
  Use webhooks as the primary notification method. Polling every 15–30 seconds is appropriate as a backup.
</Tip>

## Webhooks for Status Updates

Subscribe to the `PAYOUT` event type to receive real-time notifications on every status change:

```javascript theme={null}
// Subscribe to payout events
await fetch('/api/v2/webhooks', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    url: 'https://your-app.com/webhooks/killb',
    secret: 'your-webhook-secret',
    events: ['PAYOUT'],
    active: true
  })
});
```

Handle incoming payout webhook events:

```javascript theme={null}
app.post('/webhooks/killb', (req, res) => {
  const { event, data } = req.body;

  switch (event) {
    // Phase progress events — update your UI status indicator
    case 'payout.cash_in_pending':
    case 'payout.cash_in_processing':
    case 'payout.cash_in_completed':
    case 'payout.kyt_out_pending':
    case 'payout.kyt_out_processing':
    case 'payout.kyt_out_completed':
    case 'payout.cash_out_pending':
    case 'payout.cash_out_processing':
    case 'payout.cash_out_completed':
    case 'payout.refund_pending':
    case 'payout.refund_processing': {
      const status = event.replace('payout.', '').toUpperCase();
      updatePayoutStatus(data.id, status);
      break;
    }

    case 'payout.completed':
      updatePayoutStatus(data.id, 'COMPLETED');
      notifyRecipient(data);
      break;

    case 'payout.review_needed':
      // Transaction flagged for manual compliance review — no action required, payout will resume or be rejected
      updatePayoutStatus(data.id, 'REVIEW_NEEDED');
      alertComplianceTeam(data);
      break;

    case 'payout.error':
      // Internal system error — no funds disbursed
      updatePayoutStatus(data.id, 'ERROR');
      alertOpsTeam(data);
      break;

    case 'payout.failed':
      // Invalid or incorrect beneficiary details
      updatePayoutStatus(data.id, 'FAILED');
      notifyInvalidBeneficiary(data);
      break;

    case 'payout.rejected':
      // Blocked by compliance review
      updatePayoutStatus(data.id, 'REJECTED');
      escalateToCompliance(data);
      break;

    case 'payout.refunded':
      // Funds returned to pre-fund balance
      updatePayoutStatus(data.id, 'REFUNDED');
      reconcileBalance(data);
      break;
  }

  res.status(200).json({ received: true });
});
```

## Best Practices

<AccordionGroup>
  <Accordion title="Webhooks as Primary Channel" icon="bell">
    Always configure a webhook endpoint for `PAYOUT` events. This gives you instant status updates without any polling overhead and reduces unnecessary API calls.
  </Accordion>

  <Accordion title="Poll as Backup" icon="rotate">
    If your webhook delivery fails, poll `GET /api/v2/payouts/{id}` every 15–30 seconds. Set a reasonable timeout (e.g., 5 minutes) and alert your team if a payout remains in any processing state longer than expected.
  </Accordion>

  <Accordion title="Distinguish Failure States" icon="circle-xmark">
    Each non-success status has a distinct cause and remediation. Critically, the phase in which `FAILED` or `ERROR` occurs determines whether a refund is issued:

    * **`FAILED` / `ERROR` during Cash In** — Terminal. No funds were collected, so no refund is issued. For `ERROR`, contact support if it recurs.
    * **`FAILED` / `ERROR` after Cash In** (KYT Out, Cash Out) — Transient. Because funds were already collected, the payout automatically transitions to `REFUND_PENDING` → `REFUND_PROCESSING` → `REFUNDED`.
    * **`REJECTED`** — Blocked by compliance review. Do not retry automatically; escalate internally. Always leads to the refund flow since funds were already collected.
    * **`REVIEW_NEEDED`** — A KYT check flagged the transaction for manual review. The payout resumes automatically or transitions to `REJECTED` after review; no action required on your side.
  </Accordion>

  <Accordion title="Reconcile Daily" icon="calculator">
    Pull all payouts for the previous day via `GET /api/v2/payouts` and reconcile against your internal ledger. Compare expected vs. actual terminal statuses to catch any discrepancies early.

    ```javascript theme={null}
    const { payouts } = await listPayouts({ limit: 100 });
    const needsAttention = payouts.filter(p =>
      ['ERROR', 'FAILED', 'REJECTED'].includes(p.status)
    );

    if (needsAttention.length > 0) {
      console.warn(`${needsAttention.length} payouts need attention`, needsAttention);
    }
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Webhooks Setup" icon="bell" href="/guides/webhooks/setup">
    Configure and secure webhook notifications
  </Card>

  <Card title="Error Handling" icon="shield" href="/resources/error-handling">
    Handle API errors and retries gracefully
  </Card>
</CardGroup>
