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

# Pay your first employee

> An end-to-end walkthrough: add an employee, run payroll, and receive the event.

This tutorial takes you through a full loop with the Kua API — from an empty
sandbox to a disbursed payroll run you observe via a webhook. Use a **`kua_test_…`
key** so nothing moves real money.

<Note>
  Everything here runs in [test mode](/concepts/data-model#test-mode). Test data is
  isolated and auto-purges after 14 days, so experiment freely.
</Note>

## Before you start

* A `kua_test_…` API key with the `employees:write` and `payroll:write`
  [scopes](/authentication#scopes) (or `*`).
* An **employment type** to place the employee in. Employment types are created
  in the dashboard; grab its `id` from **Settings → Employment types**. We'll call
  it `EMPLOYMENT_TYPE_ID` below.
* Optionally, an SDK installed (`npm install @kua/node` or `pip install kua`).

<Steps>
  <Step title="Add an employee">
    Create the person you're going to pay. Send an
    [`Idempotency-Key`](/idempotency) so a retry can't add them twice.

    <CodeGroup>
      ```bash cURL theme={null}
      curl https://api.kuahr.com/v1/employees \
        -X POST \
        -H "X-Api-Key: kua_test_your_key_here" \
        -H "Idempotency-Key: tutorial-employee-jane" \
        -H "Content-Type: application/json" \
        -d '{
          "firstName": "Jane",
          "lastName": "Doe",
          "email": "jane@acme.com",
          "employmentTypeId": "EMPLOYMENT_TYPE_ID",
          "employedAt": "2026-07-01"
        }'
      ```

      ```ts Node theme={null}
      import { Kua } from "@kua/node";
      const kua = new Kua({ apiKey: process.env.KUA_API_KEY! });

      const employee = await kua.employees.create(
        {
          firstName: "Jane",
          lastName: "Doe",
          email: "jane@acme.com",
          employmentTypeId: process.env.EMPLOYMENT_TYPE_ID!,
          employedAt: "2026-07-01",
        },
        { idempotencyKey: "tutorial-employee-jane" },
      );
      console.log("Created employee", employee.id);
      ```

      ```python Python theme={null}
      import os
      from kua import Kua

      kua = Kua(api_key=os.environ["KUA_API_KEY"])

      employee = kua.employees.create(
          first_name="Jane",
          last_name="Doe",
          email="jane@acme.com",
          employment_type_id=os.environ["EMPLOYMENT_TYPE_ID"],
          employed_at="2026-07-01",
          idempotency_key="tutorial-employee-jane",
      )
      print("Created employee", employee.id)
      ```
    </CodeGroup>

    You get back the new employee's `id`. Set their salary structure in the dashboard
    so they're included in payroll (or via your normal onboarding flow).
  </Step>

  <Step title="Create a payroll run">
    Build a **draft** run for the month from your current roster. This does not move
    money — it stages payroll for approval.

    <CodeGroup>
      ```bash cURL theme={null}
      curl https://api.kuahr.com/v1/payroll/runs \
        -X POST \
        -H "X-Api-Key: kua_test_your_key_here" \
        -H "Idempotency-Key: tutorial-run-2026-07" \
        -H "Content-Type: application/json" \
        -d '{ "year": 2026, "month": 7 }'
      ```

      ```ts Node theme={null}
      const run = await kua.payrollRuns.create(
        { year: 2026, month: 7 },
        { idempotencyKey: "tutorial-run-2026-07" },
      );
      console.log("Draft run", run.id, run.status); // -> "Draft"
      ```

      ```python Python theme={null}
      run = kua.payroll_runs.create(
          year=2026, month=7, idempotency_key="tutorial-run-2026-07"
      )
      print("Draft run", run.id, run.status)  # -> "Draft"
      ```
    </CodeGroup>

    The run comes back with `status: "Draft"`.
  </Step>

  <Step title="Approve and disburse">
    Approval and disbursement are deliberate, owner-gated steps — done from the
    **dashboard** (or an owner-authorized flow), not with a scoped API key. This
    keeps an integration from releasing funds on its own. Approve and disburse the
    draft run there.

    As it progresses, Kua emits events: `payroll.run.hr_approved`,
    `payroll.run.disbursement_started`, `payroll.line.settled` (per employee), and
    finally `payroll.run.disbursed`.
  </Step>

  <Step title="Receive the event">
    Rather than polling, let Kua tell you when the run settles. Register a webhook
    (once), then handle the delivery.

    ```bash Register an endpoint theme={null}
    curl https://api.kuahr.com/v1/webhooks \
      -X POST \
      -H "X-Api-Key: kua_test_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://your-app.com/kua/webhooks",
        "eventTypes": "payroll.run.disbursed"
      }'
    ```

    The response includes a signing `secret` (`whsec_…`) — store it. Now handle
    deliveries, verifying each one:

    <CodeGroup>
      ```ts Node (Express) theme={null}
      import express from "express";
      import { verifySignature } from "@kua/node";

      const app = express();
      app.post("/kua/webhooks", express.raw({ type: "application/json" }), (req, res) => {
        const ok = verifySignature(req.body, req.header("X-Kua-Signature"), process.env.KUA_WEBHOOK_SECRET!);
        if (!ok) return res.status(400).end();

        const event = JSON.parse(req.body.toString());
        if (event.type === "payroll.run.disbursed") {
          console.log("Run settled:", event.data.runId, "→", event.data.settled, "paid");
        }
        res.status(200).end();
      });
      ```

      ```python Python (Flask) theme={null}
      import os
      from flask import Flask, request, abort
      from kua import verify_signature

      app = Flask(__name__)

      @app.post("/kua/webhooks")
      def receive():
          if not verify_signature(request.get_data(), request.headers.get("X-Kua-Signature"), os.environ["KUA_WEBHOOK_SECRET"]):
              abort(400)
          event = request.get_json()
          if event["type"] == "payroll.run.disbursed":
              print("Run settled:", event["data"]["runId"], "→", event["data"]["settled"], "paid")
          return "", 200
      ```
    </CodeGroup>

    Not ready to stand up a public endpoint? Fire a test delivery at your handler
    with `POST /v1/webhooks/{id}/ping`, or just poll `GET /v1/events`.
  </Step>
</Steps>

## What you built

You added an employee, staged a payroll run, and wired up a verified, real-time
event handler — the backbone of any Kua integration. From here:

<CardGroup cols={2}>
  <Card title="Webhooks overview" icon="bolt" href="/webhooks/overview">
    Delivery, retries, and the full event catalog.
  </Card>

  <Card title="API reference" icon="code" href="/api-reference">
    Every endpoint with a live playground.
  </Card>
</CardGroup>
