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

# Pay a Contractor End-to-End

> The complete journey — onboard a contractor, put an agreement in place, provision their wallet, run payroll, and cash out

export const ApiExample = ({method = "GET", path, params, body, reference, tenantKey}) => {
  const baseUrl = "https://api.cadanapay.com";
  const query = params ? "?" + Object.entries(params).map(([k, v]) => `${k}=${v}`).join("&") : "";
  const url = `${baseUrl}${path}${query}`;
  const isPlatformPath = (/^\/(v1\/)?platform(\/|$)/).test(path || "");
  const includeTenantKey = tenantKey === undefined ? !isPlatformPath : tenantKey;
  let curl = `curl -X ${method} '${url}' \\\n  -H 'Authorization: Bearer YOUR_API_KEY'`;
  if (includeTenantKey) {
    curl += ` \\\n  -H 'X-MultiTenantKey: YOUR_BUSINESS_TENANT_KEY'`;
  }
  if (body) {
    const formatted = JSON.stringify(body, null, 2);
    curl += ` \\\n  -H 'Content-Type: application/json' \\\n  -d '${formatted}'`;
  }
  return <div>
      <CodeBlock language="bash" filename="bash" wrap>
        {curl}
      </CodeBlock>
      {reference && <div style={{
    marginTop: "-0.5rem",
    marginBottom: "1rem"
  }}>
          <a href={reference} style={{
    fontSize: "0.875rem"
  }}>
            Try it in the playground →
          </a>
        </div>}
    </div>;
};

This guide walks the full lifecycle of paying an independent contractor through Cadana, start to finish. Each step links to a deeper guide for full field references — this page is the map. (Hiring an employee instead? Employees add statutory compliance and a tax profile — see [Pay an Employee End-to-End](/workforce/pay-an-employee).)

```mermaid theme={null}
flowchart LR
    A[1. Onboard<br/>Person] --> B[2. Agreement<br/>optional]
    A --> C[3. Wallet<br/>User + KYC]
    C --> D[4. Pay<br/>Payroll]
    D --> E[5. Cash out<br/>UI or API]
```

***

## Prerequisites

* An API key from the [Dashboard](https://app.cadanapay.com) — see [Authentication](/authentication)
* A funded business account — see [Fund Your Account](/fund-your-account), or use the [sandbox deposit endpoint](/api-reference/workforce/sandbox/deposit) for test funds

<Tip>
  Building against the sandbox first? Read [Sandbox & Testing](/reference/sandbox) — it covers test funding, KYC sentinel values, and resetting users.
</Tip>

***

## Step 1: Onboard the Contractor

Create a [Person](/workforce/onboarding-workers) with `type: "CONTRACTOR"`. Contractors are either individuals or registered businesses — set `contractorType` accordingly (`businessName` is required for `BUSINESS`).

<Tabs>
  <Tab title="Individual">
    <ApiExample method="POST" path="/v1/persons" body={{ type: "CONTRACTOR", contractorType: "INDIVIDUAL", firstName: "Amara", lastName: "Okafor", email: "amara@example.com", address: { line1: "500 Mission St", city: "San Francisco", state: "CA", postalCode: "94105", countryCode: "US" }, compInfo: { type: "net", salary: { amount: 500000, currency: "USD" }, frequency: "monthly" }, jobInfo: { title: "Frontend Developer", startDate: "2026-02-01" } }} reference="/api-reference/workforce/persons/create" />
  </Tab>

  <Tab title="Registered Business">
    <ApiExample method="POST" path="/v1/persons" body={{ type: "CONTRACTOR", contractorType: "BUSINESS", businessName: "Okafor Consulting Ltd", firstName: "Amara", lastName: "Okafor", email: "amara@example.com", address: { line1: "500 Mission St", city: "San Francisco", state: "CA", postalCode: "94105", countryCode: "US" }, compInfo: { type: "net", salary: { amount: 800000, currency: "USD" }, frequency: "monthly" } }} reference="/api-reference/workforce/persons/create" />
  </Tab>
</Tabs>

The response is the `personId` you'll use in every later step:

```json theme={null}
{
  "id": "8ef9a712-cdae-4110-b1ea-9ba95abbee6e"
}
```

Two things to get right here:

* **Amounts are in minor units** — `500000` USD is \$5,000.00.
* **`compInfo` is what payroll pays.** For contractors, use type `net` (or `fixed`). If pay varies per cycle (hourly work, milestones), set `salary.amount` to `0` and supply the real amount on each payroll entry in [Step 4](#step-4-run-payroll).

See [Onboard Workers](/workforce/onboarding-workers) for all optional fields (departments, custom fields, metadata).

***

## Step 2: Put an Agreement in Place (Optional)

Payroll does not require a contract — but most businesses want one on file. Cadana supports two flows; both attach the agreement to the contractor's `personId`. See [Manage Contracts](/workforce/manage-contracts) for the full lifecycle.

<Tabs>
  <Tab title="Generate from a template">
    List your [contract templates](/api-reference/workforce/contracts/list-templates), then create a contract for the contractor. Template variables (name, salary, dates) are filled from the Person record at creation time.

    <ApiExample method="POST" path="/v1/contracts" body={{ templateId: "c47f3b21-9a8e-4d5c-b6f2-1e0a9d8c7b6a", personId: "8ef9a712-cdae-4110-b1ea-9ba95abbee6e", sender: { name: "HR Department", email: "hr@example.com" } }} reference="/api-reference/workforce/contracts/create" />

    Both parties then sign electronically — fetch each signer's link with [`GET /v1/contracts/{contractId}/signingUrl`](/api-reference/workforce/contracts/signing-url) and track progress via the contract `status` (`awaiting business signature` → `awaiting person signature` → `completed`).
  </Tab>

  <Tab title="Upload a signed PDF">
    Already signed elsewhere? [Upload the PDF](/reference/working-with-files) with purpose `contract`, then attach it:

    <ApiExample method="POST" path="/v1/contracts-upload" body={{ personId: "8ef9a712-cdae-4110-b1ea-9ba95abbee6e", fileId: "e63f5b24-8c7d-4a9e-b0f1-3d4e6f8a9b0c", contractName: "Contractor Agreement - Amara Okafor" }} reference="/api-reference/workforce/contracts/upload-contract" />

    Uploaded contracts are recorded with status `completed` immediately.
  </Tab>
</Tabs>

<Note>
  **The agreement documents the terms; it doesn't drive payment.** Payroll amounts come from the Person's `compInfo` (or per-cycle entries), and payroll runs regardless of contract status. Template variables snapshot the Person's data when the contract is created — updating `compInfo` later does not update an existing contract.
</Note>

***

## Step 3: Give Them a Wallet

Contractors are paid into a [Cadana Global Wallet](/wallets/overview) by default. Provisioning takes two calls — full details in [Set Up Worker Wallets](/wallets/worker-wallets).

**1. Create a User** for the Person. This provisions a wallet in their compensation currency and automatically sets `wallet` as their preferred payment method:

<ApiExample method="POST" path="/v1/users/invite" body={{ personId: "8ef9a712-cdae-4110-b1ea-9ba95abbee6e" }} reference="/api-reference/workforce/users/invite" />

By default the contractor receives a welcome email to set their password. Pass `suppressWelcomeEmail: true` if you handle login via [your own SSO](/white-label/custom-authentication).

**2. Submit KYC.** The wallet can't receive funds until identity verification is approved. Upload the ID documents ([file upload flow](/reference/working-with-files)), then submit — `issuedBy`, `frontFileId`, and `selfieFileId` are required:

<ApiExample method="POST" path="/v1/users/3a1b2c3d-4e5f-6789-abcd-ef0123456789/kyc" body={{ firstName: "Amara", lastName: "Okafor", dob: "1992-03-14", nationality: "US", address: { line1: "500 Mission St", city: "San Francisco", state: "CA", postalCode: "94105", countryCode: "US" }, idDetails: { type: "passport", number: "545678921", issuedBy: "U.S. Department of State", issuedDate: "2019-06-01", expirationDate: "2029-06-01", issuedCountryCode: "US", frontFileId: "91ac9d37-2c6c-4d24-be19-8c03fbd5538b", selfieFileId: "bbf49bbe-9d3d-41a7-add7-fb8abe698ad2" } }} reference="/api-reference/workforce/users/submit-kyc" />

Track approval by [polling KYC status](/api-reference/workforce/users/get-kyc) or subscribing to the `user.kyc.updated` [webhook](/reference/events). Alternatively, the contractor can complete KYC themselves in the Cadana app after logging in. See [KYC Verification](/platform/kyc-verification) for statuses, rejections, and resubmission.

<Warning>
  **In sandbox, use the sentinel values** — `idDetails.number: "auto-approve"` and `address.line2: "auto-approve"`. Non-sentinel submissions are never resolved; see [KYC Testing](/reference/sandbox#kyc-testing).
</Warning>

<Note>
  Contractors hired by U.S. businesses also complete a tax form (W-9, W-8BEN, or W-8BEN-E) in the Cadana app alongside KYC — no API work needed on your side. See [Tax Forms](/wallets/tax-forms).
</Note>

Confirm the wallet is ready:

<ApiExample method="GET" path="/v1/users/3a1b2c3d-4e5f-6789-abcd-ef0123456789/wallets" reference="/api-reference/wallets/balances/get-user-wallets-balances" />

<Tip>
  **Skipping the wallet?** If direct payouts are enabled for your business, you can pay a contractor's bank, ACH, or mobile-money account instead — no User account needed. Set it with [`PUT /v1/persons/{personId}/paymentInfo`](/api-reference/workforce/persons/update-payment-information); see [Onboard Workers](/workforce/onboarding-workers#direct-payment-methods-alternative). Direct methods require enablement by your account manager.
</Tip>

***

## Step 4: Run Payroll

Pay the contractor with a one-off contractor payroll — full details in [Pay Workers via Payroll](/workforce/pay-workers-via-payroll).

**1. Create** the payroll shell:

<ApiExample method="POST" path="/v1/payrolls" body={{ workerType: "CONTRACTOR", type: "ONE_OFF" }} reference="/api-reference/workforce/payrolls/create" />

**2. Save** the entries. The `salary` here is what gets paid this cycle (minor units):

<ApiExample method="POST" path="/v1/payrolls/a1b2c3d4-e5f6-7890-abcd-ef0123456789/save" body={{ payrollDate: "2026-02-28", payPeriod: { fromDate: "2026-02-01", toDate: "2026-02-28" }, entries: [{ personId: "8ef9a712-cdae-4110-b1ea-9ba95abbee6e", salary: { amount: 100000, currency: "USD" } }] }} reference="/api-reference/workforce/payrolls/save" />

Saving is asynchronous — poll [`GET /v1/payrolls/{payrollId}`](/api-reference/workforce/payrolls/get) until the status is `Pending Submission`, then review the calculated `debit`: the total that will leave your business account, i.e. net pay plus fees. The fee breakdown is on the linked invoice ([`GET /v1/invoices/{invoiceId}`](/api-reference/workforce/invoices/get-invoice)).

**3. Approve** to trigger fund collection and disbursement:

<ApiExample method="POST" path="/v1/payrolls/a1b2c3d4-e5f6-7890-abcd-ef0123456789/approve" reference="/api-reference/workforce/payrolls/approve" />

**4. Confirm the money landed.** When the payroll reaches `Completed`, each entry carries `transactionIds` — resolve them with [`GET /v1/users/{userId}/transactions/{transactionId}`](/api-reference/wallets/transactions/get-transaction) or check the [wallet balance](/api-reference/wallets/balances/get-user-wallets-balances). Wallet credits can trail the `Completed` status by up to a minute.

<Tip>
  Distribute earning statements with [payslip links](/workforce/pay-workers-via-payroll#downloading-payslips), and reconcile fees against the payroll's [invoice](/api-reference/workforce/invoices/get-invoice).
</Tip>

***

## Step 5: The Contractor Gets Their Money

Funds sit in the contractor's wallet until they're withdrawn. Two paths:

### Self-serve (Cadana or white-label app)

The contractor logs in — via the welcome-email credentials or [your SSO](/white-label/custom-authentication) — and cashes out to a bank account, mobile money, or card in 100+ countries from the app. Nothing to build; brand it with [White-Label UI](/white-label/overview).

### On their behalf (API)

Your platform can run the payout for them — full details in [Payout](/wallets/payout):

1. Check the corridor: [payment methods](/api-reference/payments/resources/get-payment-methods) for the destination country, [field requirements](/api-reference/payments/resources/payment-corridor-requirements) for the details schema, and [fees, timelines, and limits](/api-reference/wallets/transactions/get-user-fees).
2. [Create a beneficiary](/api-reference/wallets/beneficiaries/create-beneficiary) — the saved payment destination.
3. [Get an FX quote](/api-reference/payments/fx/create-fx-quote). A quote is required even when source and destination currencies are the same (the rate is `1`).
4. Create the transaction. `reference` is your idempotency key and must be a UUID:

<ApiExample method="POST" path="/v1/users/3a1b2c3d-4e5f-6789-abcd-ef0123456789/transactions" body={{ type: "payout", beneficiaryId: "6da7d084-315c-43a5-820f-4b9fdb17c21a", quoteId: "af704b6b-0584-4fb1-a32f-d4f6bc6d3d9b", sourceAmount: { value: "500.00", currency: "USD" }, reference: "e2526405-0d01-4d81-8818-6cd06131d735" }} reference="/api-reference/wallets/transactions/create-transaction" />

5. [Track the transaction](/api-reference/wallets/transactions/get-transaction) to `success`, or subscribe to `transaction.succeeded` / `transaction.failed` [webhooks](/reference/events).

<Note>
  Wallet transaction amounts are decimal strings in **major** units (`"500.00"` = \$500.00) — unlike the minor-unit integers used by payroll. See [Money Amounts](/reference/api-overview#money-amounts).
</Note>

***

## Recap

| Step      | Endpoint                                                                                                                                               | You have                       |
| :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------- |
| Onboard   | [`POST /v1/persons`](/api-reference/workforce/persons/create)                                                                                          | `personId`                     |
| Agreement | [`POST /v1/contracts`](/api-reference/workforce/contracts/create) or [`POST /v1/contracts-upload`](/api-reference/workforce/contracts/upload-contract) | signed agreement on file       |
| Wallet    | [`POST /v1/users/invite`](/api-reference/workforce/users/invite) + [`POST /v1/users/{userId}/kyc`](/api-reference/workforce/users/submit-kyc)          | `userId`, KYC-approved wallet  |
| Pay       | [`POST /v1/payrolls`](/api-reference/workforce/payrolls/create) → save → approve                                                                       | funds in the wallet            |
| Cash out  | self-serve app, or [`POST /v1/users/{userId}/transactions`](/api-reference/wallets/transactions/create-transaction)                                    | funds at the contractor's bank |

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Onboard Workers" icon="user-plus" href="/workforce/onboarding-workers">
    Full Person field reference
  </Card>

  <Card title="Payroll Lifecycle" icon="arrows-rotate" href="/workforce/payroll-lifecycle">
    All payroll statuses and webhooks
  </Card>

  <Card title="Set Up Worker Wallets" icon="wallet" href="/wallets/worker-wallets">
    Users, KYC, and wallet details
  </Card>

  <Card title="Payout" icon="paper-plane" href="/wallets/payout">
    Beneficiaries, quotes, and corridors
  </Card>
</CardGroup>
