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 approval
  • debitAmount - 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

StepAPI EndpointMethodWebhook Event
Create/v1/payrollsPOSTpayroll.created
Save/v1/payrolls/{id}/savePOSTpayroll.status.updated (status: saved)
Approve/v1/payrolls/{id}/approvePOSTpayroll.status.updated (status: approved)
Get Details/v1/payrolls/{id}GET-
List/v1/payrollsGET-
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:

  1. Fresh FX rates are fetched from the market
  2. All entry amounts are recalculated with new rates
  3. debitAmount is updated
  4. 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:

FieldTypeDescription
debitAmountAmount objectCalculated debit in funding currency (indicative - may change)
recommendedFundingAmountAmount objectDebitAmount + FX buffer to prevent insufficient funds if rates move
fxBufferPercentfloatBuffer percentage applied (e.g., 0.03 for 3%)
fxRatesmapCurrency pair rates used for calculation

Recommended Approach

  1. Fund the recommended amount: The recommendedFundingAmount includes an optional buffer. Funding this amount reduces the chance of insufficient funds if rates move unfavorably.

  2. Listen for rates_refreshed webhooks: If you receive this webhook, the debitAmount has changed. Poll the payroll to see the updated amounts.

  3. 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.