Payroll Lifecycle
Payroll API Flow for Developers
This document describes the complete API flow for managing payrolls, including all API calls and webhooks.
Overview
sequenceDiagram
participant App as Your App
participant Cadana
participant Webhook
App->>Cadana: 1. POST /v1/payrolls
Cadana->>Webhook: payroll.created
App->>Cadana: 2. POST /v1/payrolls/{id}/save
Cadana->>Webhook: payroll.status.updated (saved)
App->>Cadana: 3. POST /v1/payrolls/{id}/approve
Cadana->>Webhook: payroll.status.updated (approved)
Note over Cadana: Automatic (if balance sufficient)
Cadana->>Webhook: payroll.status.updated (scheduled)
Note over Cadana: Automatic (on disbursement date)
Cadana->>Webhook: payroll.status.updated (processing)
Note over Cadana: Automatic
Cadana->>Webhook: payroll.status.updated (completed)
Only 3 API calls needed: Create → Save → Approve. Everything else is automatic.
Step-by-Step Flow
Step 1: Create Payroll
API Call:
POST /v1/payrolls
Request Body:
{
"name": "August 2024 Payroll",
"workerType": "CONTRACTOR",
"type": "ONE_OFF",
"datePayable": "2024-08-15",
"payPeriod": {
"from": "2024-08-01",
"to": "2024-08-31"
}
}Response: Payroll object with id and status: "created"
Webhook Received:
{
"eventType": "payroll.created",
"payload": {
"id": "payroll-uuid",
"workerType": "CONTRACTOR",
"type": "ONE_OFF",
"tenantKey": "business-uuid",
"timestamp": "2024-08-01T10:00:00Z"
}
}Step 2: Save Payroll (Add Entries)
API Call:
POST /v1/payrolls/{id}/save
Request Body:
{
"entries": [
{
"personId": "person-uuid-1",
"netAmount": { "currency": "USD", "value": 5000 }
},
{
"personId": "person-uuid-2",
"netAmount": { "currency": "USD", "value": 4500 }
}
]
}API Response: Payroll object including:
status: "saved"- Ready for approvaldebitAmount- Total amount to be debited from business wallet
Webhook Received:
{
"eventType": "payroll.status.updated",
"payload": {
"id": "payroll-uuid",
"status": "saved",
"tenantKey": "business-uuid",
"timestamp": "2024-08-01T10:05:00Z"
}
}Step 3: Approve Payroll
API Call:
POST /v1/payrolls/{id}/approve
Request Body: (empty or with approver details)
API Response: Payroll object with status: "approved"
Webhook Received:
{
"eventType": "payroll.status.updated",
"payload": {
"id": "payroll-uuid",
"status": "approved",
"tenantKey": "business-uuid",
"timestamp": "2024-08-01T10:10:00Z"
}
}What happens next (automatic):
- Cadana checks if business has sufficient balance
- If sufficient → Payroll moves to
scheduled - If insufficient → Payroll stays in "Awaiting Funds" until business adds funds
Step 4: Scheduled (Automatic)
No API call needed - This happens automatically after approval if balance is sufficient.
Webhook Received:
{
"eventType": "payroll.status.updated",
"payload": {
"id": "payroll-uuid",
"status": "scheduled",
"tenantKey": "business-uuid",
"timestamp": "2024-08-01T10:15:00Z"
}
}Step 5: Processing (Automatic)
No API call needed - Disbursements are triggered automatically on the scheduled date.
Webhook Received:
{
"eventType": "payroll.status.updated",
"payload": {
"id": "payroll-uuid",
"status": "processing",
"tenantKey": "business-uuid",
"timestamp": "2024-08-15T09:00:00Z"
}
}Step 6: Completed (Automatic)
No API call needed - Payroll completes when all disbursements are confirmed.
Webhook Received:
{
"eventType": "payroll.status.updated",
"payload": {
"id": "payroll-uuid",
"status": "completed",
"tenantKey": "business-uuid",
"timestamp": "2024-08-15T09:30:00Z"
}
}State Diagram
flowchart TD
A[Created] -->|POST /v1/payrolls/id/save| B[Saved]
B -->|POST /v1/payrolls/id/approve| C[Approved]
C -->|Balance OK| D[Scheduled]
C -->|Insufficient balance| E[Awaiting Funds]
E -->|When funded| D
D -->|Disbursement triggered| F[Processing]
F -->|All disbursements confirmed| G[Completed]
API Reference Summary
| Step | API Endpoint | Method | Webhook Event |
|---|---|---|---|
| Create | /v1/payrolls | POST | payroll.created |
| Save | /v1/payrolls/{id}/save | POST | payroll.status.updated (status: saved) |
| Approve | /v1/payrolls/{id}/approve | POST | payroll.status.updated (status: approved) |
| Get Details | /v1/payrolls/{id} | GET | - |
| List | /v1/payrolls | GET | - |
| Schedule | (automatic) | - | payroll.status.updated (status: scheduled) |
| Disburse | (automatic) | - | payroll.status.updated (status: processing) |
| Complete | (automatic) | - | payroll.status.updated (status: completed) |
Webhook Status Values
The status field in payroll.status.updated webhooks will contain one of these values:
"saved"- Payroll saved with entries"approved"- Payroll approved"scheduled"- Payroll scheduled for disbursement"processing"- Disbursements being triggered"completed"- Payroll completed
Multi-Currency Payrolls
This section applies when the contract currency (the currency specified in the worker's contract) differs from the funding currency (the currency the business uses to fund their wallet). For example, a US company (funding in USD) paying a contractor whose contract is in BRL.
Note: The final payment currency to the worker is separate and does not affect this flow.
How It Works
For multi-currency payrolls, FX rates are captured when the payroll is saved. If there is a delay before disbursement (e.g., the business funds their wallet later, or the disbursement date is in the future), rates may need to be refreshed at disbursement time.
When this happens:
- Fresh FX rates are fetched from the market
- All entry amounts are recalculated with new rates
debitAmountis updated- Balance is re-checked:
- If sufficient → Disbursement proceeds
- If insufficient → Payroll moves to "Awaiting Funds"
State Diagram (Multi-Currency)
flowchart TD
A[Created] -->|POST /v1/payrolls/id/save<br/>FX rates captured| B[Saved]
B -->|POST /v1/payrolls/id/approve| C[Approved]
C -->|Balance OK| D[Scheduled]
C -.->|Insufficient balance| E[Awaiting Funds]
E -.->|When funded| D
subgraph Disbursement Time
D --> F{FX rates<br/>expired?}
F -->|No| G[Processing]
F -->|Yes| H[FX Rate Refresh]
H --> I{Balance OK?}
I -->|Yes| G
I -->|No| E
end
G -->|All disbursements confirmed| J[Completed]
Additional Webhook
For multi-currency payrolls, you may receive an additional webhook:
{
"eventType": "payroll.status.updated",
"payload": {
"id": "payroll-uuid",
"status": "rates_refreshed",
"tenantKey": "business-uuid",
"timestamp": "2024-08-15T09:00:00Z"
}
}This indicates FX rates were refreshed. Poll GET /v1/payrolls/{id} to see the updated debitAmount.
Additional Response Fields
For multi-currency payrolls, the save response includes:
| Field | Type | Description |
|---|---|---|
debitAmount | Amount object | Calculated debit in funding currency (indicative - may change) |
recommendedFundingAmount | Amount object | DebitAmount + FX buffer to prevent insufficient funds if rates move |
fxBufferPercent | float | Buffer percentage applied (e.g., 0.03 for 3%) |
fxRates | map | Currency pair rates used for calculation |
Recommended Approach
-
Fund the recommended amount: The
recommendedFundingAmountincludes an optional buffer. Funding this amount reduces the chance of insufficient funds if rates move unfavorably. -
Listen for
rates_refreshedwebhooks: If you receive this webhook, thedebitAmounthas changed. Poll the payroll to see the updated amounts. -
Monitor for "Awaiting Funds": If the balance is insufficient after rate refresh, the payroll will not proceed until the business funds their wallet.
Example Timeline
Day 1, 10:00 AM POST /v1/payrolls/{id}/save
- debitAmount: $10,000 USD (indicative)
- recommendedFundingAmount: $10,300 USD (with 3% buffer)
Day 1, 10:05 AM POST /v1/payrolls/{id}/approve
- Payroll approved and scheduled
Day 3, 9:00 AM Disbursement time
- FX rates refreshed (rates moved slightly)
- New debitAmount: $10,150 USD
- Balance check: OK
- Webhook: status.updated (status: "rates_refreshed")
- Webhook: status.updated (status: "processing")
- Webhook: status.updated (status: "completed")
For questions, contact Cadana support.
Updated 17 days ago
