This guide details the steps needed for a business to pay its workers via a scheduled payroll. Cadana handles fund collection, scheduling, and disbursement automatically.Documentation Index
Fetch the complete documentation index at: https://docs.cadanapay.com/llms.txt
Use this file to discover all available pages before exploring further.
Prerequisites
API key from Dashboard
Get your API key from the Cadana Dashboard. See Authentication for details.
Compliance-approved business
Your business must have completed KYB verification. See KYB Requirements.
Account funded
Your business account must have sufficient funds to cover the payroll. See Fund Your Account.
Onboarded workers
Workers must be onboarded as Persons with payment methods configured — you’ll use their
personId to set up pay entries. See Onboard Workers.Step 1: Create a Payroll
Start by creating an empty payroll shell. Specify the worker type and whether this is a one-off or regular payroll. Response:payrollId — you’ll use it in the next steps.
Payroll Types
| Field | Values | Description |
|---|---|---|
workerType | EMPLOYEE, CONTRACTOR | Type of workers being paid |
type | ONE_OFF, REGULAR | One-off payment or recurring payroll |
Step 2: Save Entries
Add the pay date, pay period, and compensation entries for each worker. Each entry requires apersonId and salary amount.
If you set compensation info (compInfo) when onboarding the worker, you can retrieve it from the Person profile with GET /v1/persons/{personId} and use those values for the salary entry.
Returns 204 on success. The payroll status moves to saved. Fetch the payroll with GET /v1/payrolls/{id} to see the calculated debit — the total amount that will be debited from the business account.
Amounts are in the lowest denomination of the currency. For USD,
100000 = $1,000.00. For GHS, 500000 = GHS 5,000.00.Per-Cycle Compensation Fields
BeyondpersonId and salary, each entry accepts per-cycle compensation overrides. Fields are optional — send what’s relevant to the cycle.
| Field | Type | Description |
|---|---|---|
bonus | Money | Bonus paid this period. |
commission | Money | Commission paid this period. |
allowances | Allowance[] | Non-statutory allowance line items (e.g., housing, transport stipends). |
deductions | Deduction[] | Voluntary deduction line items (e.g., pension top-ups, garnishments, equipment repayments). |
timeWorked | number | Units of work this period (hours, days, or 1 for salaried-monthly). The unit is implied by the person’s compInfo.frequency — never sent on the entry. Defaults to 1 when omitted. |
overtime | Money | Overtime rate (when paired with overtimeWorked) or a flat overtime amount (when sent alone). |
overtimeWorked | number | Overtime units worked this period. Multiplies against overtime when both are sent. Must be non-negative. |
$5,000 monthly base + $500 bonus + $250 commission + $200 wellness stipend + $100 internet stipend − $150 voluntary pension. Statutory items (income tax, social security, etc.) are computed by the engine and returned on GET.
Frequency is always sourced from the person’s stored
compInfo.frequency. Sending it on the entry is unsupported — adjust the person’s frequency via PUT /v1/persons/{personId}/jobInfo if it needs to change.Tax profile information are person-level, not per-cycle. Set them once via
PUT /v1/persons/{personId}/taxProfileCustom Metadata
You can pass atags object on both the create and save requests to store custom metadata against the payroll (e.g., internal reference IDs, department codes). This is a free-form object — any key-value pairs you include will be persisted and returned when you retrieve the payroll.
Custom Fees (Platforms)
Platforms can pass acustomFees array on the save request to add platform-specific fee line items to the payroll invoice. Each item requires a name and amount. These are additive — they supplement standard per-seat subscription fees and are included in the total amount collected by Cadana on your behalf.
Custom fee items do not participate in revenue share calculations. Validation returns
400 for empty names, non-positive amounts, or missing currency.Save Errors
When saving entries fails validation, the API returns400 with an invalid_request_body code. Each error in the params object is prefixed with a [ErrorType] tag you can use to programmatically filter or map errors.
Response format:
| Error Type | Description |
|---|---|
NoPaymentMethod | Person has no payment method configured |
PersonNotFound | Person ID does not match any person in the system |
EmptyWalletDetails | Wallet details are missing when payment method is wallet |
InvalidPreferredMethod | Preferred method is not a supported type |
RestrictedCountry | Beneficiary country is restricted for payments |
InvalidBankDetails | Bank payment validation failed (e.g., missing account name, invalid IBAN) |
InvalidMomoDetails | Mobile money validation failed (e.g., missing phone number, provider) |
InvalidWalletDetails | Wallet validation failed (e.g., invalid currency) |
InvalidCryptoDetails | Crypto wallet validation failed (e.g., missing address, unsupported chain) |
InvalidAchDetails | ACH payment validation failed |
InvalidSwiftDetails | SWIFT payment validation failed |
InvalidCardDetails | Card payment validation failed |
InvalidProxyDetails | Proxy payment validation failed |
InvalidPaymentDetails | Catch-all for unrecognized payment method types |
Step 3: Approve
Once you’re satisfied with the entries, approve the payroll. This triggers fund collection and schedules disbursement. Returns204 on success. The payroll status moves to approved.
After approval:
- If the business has insufficient funds, the payroll moves to
awaiting funds. If a bank account is connected, an ACH debit initiates automatically. - If the scheduled date is in the future, the payroll will be processed on the scheduled day.
- Once funds are available, the payroll processes and disburses payments to workers automatically.
Tracking Payroll Status
Poll the payroll to check its current status and see calculated totals. The response shape depends on the payroll’sworkerType — see Response Shape by Worker Type below.
Contractor response:
Response Shape by Worker Type
ForEMPLOYEE payrolls, the response also includes withholding aggregates at the top level and per-entry line items so you can reconcile gross-to-net for each worker. None of these fields appear on CONTRACTOR payrolls.
Aggregate-level (employee payrolls only):
| Field | Type | Description |
|---|---|---|
tax | Money | Total tax withheld across all entries |
pension | Money | Total pension contribution across all entries |
statutoryDeductions | Money | Total of all legally-mandated deductions (income tax, social security, etc.) |
employerContributions | Money | Total employer-side contributions (e.g., employer social security) |
| Field | Type | Description |
|---|---|---|
allowances | Allowance[] | Allowance line items (e.g., Transport, Housing). Omitted when empty. |
deductions | Deduction[] | Deduction line items. isStatutory distinguishes legally-mandated items from voluntary ones. Omitted when empty. |
employerContributions | Deduction[] | Employer-side contribution line items. Omitted when empty. |
Allowance and Deduction line-item shapes are the same ones returned by POST /v1/tax/calculate and POST /v1/tax/estimate.
Employee response example:
Entry-level
gross and net are omitted until the entry has been calculated. New aggregate and per-entry fields are additive — clients that don’t read them are unaffected.Downloading Payslips
Distribute earning statements — gross pay, withholdings, deductions, and net pay — to workers by fetching presigned download URLs for each payslip PDF. Once a payroll is approved and payslips have been generated, callGET /v1/payrolls/{payrollId}/payslip-links. By default the response returns one URL per person.
personId and paystubId so you can match the payslip back to the worker and pay period in your own system before delivery. personName is included for display.
To receive a single archive of every ready payslip instead, pass format=zip. The response then contains a single zipUrl rather than the per-person links array — useful when handing a full payroll’s payslips to a finance team in one download.
URLs expire shortly after issuance (see
expiresAt) — download or rehost them promptly rather than storing them in long-lived UI. Payslips still rendering appear under notReady with status: "generating"; retry the request once generation finishes to pick them up.List All Payrolls
Response:Status Flow
| Status | Meaning |
|---|---|
created | Empty payroll shell, no entries yet |
saved | Entries added, ready for approval |
approved | Approved, Cadana is collecting funds |
awaiting funds | Insufficient balance — payroll will proceed once funded |
scheduled | Funds collected, scheduled for the payroll date |
processing | Disbursements being sent to workers |
completed | All disbursements confirmed |
Webhooks
Subscribe to payroll and transaction events to track progress without polling. Payroll status changes:JSON
transaction.initiated, transaction.succeeded, transaction.failed) fire for each worker’s payment.
See Webhooks to configure your endpoint and Events for all event types.
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
401 Unauthorized | Invalid or missing API key | Check your API key in the Dashboard |
400 on save | Missing required fields | Ensure payrollDate, payPeriod, and at least one entry with personId and salary are provided |
Stuck in awaiting funds | Insufficient business account balance | Fund your business account, then the payroll will proceed automatically |
| Payroll won’t approve | Entries not saved | Save entries (Step 2) before approving |
Next Steps
Payroll Lifecycle
Full status flow, webhooks, and multi-currency handling
Onboard Workers
Set up Person records and payment methods
Webhooks
Real-time notifications for payroll and transactions
Workforce API Reference
Full API documentation