Waazn DevelopersOpen Integrations

Waazn API for finance and HR systems

Send approved expense claims, their receipts and company card spending from Waazn Business into your accounting system, your HR or payroll system, or both.

1. EmployeeSubmits a payment from the Waazn app, with a note and a receipt photo.
2. CompanyApproves or rejects it in waazn.com/app.
3. WaaznSends claim.approved to your finance endpoint, HR endpoint, or both.
4. Your systemBooks the expense or reimbursement, then reports back so Waazn shows it as booked.

Base URL: https://waazn.com/api/v1 · JSON over HTTPS · times are ISO 8601 in UTC · amounts are decimal numbers with an ISO 4217 currency.

Keys and permissions

An owner or admin creates keys in waazn.com/app → Integrations. Each key belongs to one company and is shown once. Send it as a bearer token:

curl https://waazn.com/api/v1/me \
  -H "Authorization: Bearer wz_live_..."
PermissionAllows
readRead claims, receipts, employees and expense account codes. Every key has it.
writeReport a claim as booked, paid or failed in your system. Set employee codes.
spendingRead company card payments.

Use one key per connected system, named after it (for example "Zoho Books" or "Bayzat HR"), so you can revoke one without touching the others.

Expense claims

GET/claims

Lists claims, oldest first. Page with after = the next_after value of the previous page.

QueryMeaning
statuspending, approved, rejected, withdrawn; comma-separated for several.
not_exported_tofinance, hr or other: only claims your system hasn't booked yet. The simplest way to poll.
updated_sinceSubmitted, decided or given a receipt at or after this time.
limit, after1–200 (default 50); cursor.
curl "https://waazn.com/api/v1/claims?status=approved&not_exported_to=finance" \
  -H "Authorization: Bearer wz_live_..."
{
  "object": "list",
  "data": [ { "id": 412, "object": "claim", "status": "approved", ... } ],
  "has_more": false,
  "next_after": null
}

GET/claims/{id}

One claim. See the claim object.

receipt_scan is what Waazn read from the receipt photo: merchant, date, total, VAT amount and rate, and the seller's VAT number. check compares it with the payment: match, amount, date, both, no_total or unreadable; difference is receipt total minus the payment. While it's being read, only status (queued, running or failed) is set; with no receipt it's null.

GET/claims/{id}/receipt

Redirects to the receipt photo (JPEG or PNG). Every claim object also carries receipt.url: a signed link that works without a key for 7 days, so your system can attach the file to the bill or expense record.

Report back: booked or paid

POST/claims/{id}/exports

Tell Waazn what your system did with an approved claim. Owners see it next to the claim, and other connected systems get a claim.exported webhook. For example, HR learns that finance booked it. Calling again for the same destination updates the record.

curl -X POST https://waazn.com/api/v1/claims/412/exports \
  -H "Authorization: Bearer wz_live_..." -H "Content-Type: application/json" \
  -d '{"destination":"finance","system":"zoho_books","external_id":"EXP-000981","status":"exported"}'
Field
destinationfinance, hr or other. Required.
statusexported (booked), paid (reimbursed to the employee) or failed. Required.
systemYour system's name, e.g. zoho_books, quickbooks, sage50, odoo.
external_idThe record id in your system (expense, bill, journal or payroll item).
noteOptional, up to 300 characters (for example the reason it failed).

Employees

GET/employees

Everyone linked to the company: card holders and employees who send claims. Each has a Waazn id, email and an optional employee_code.

PATCH/employees/{id}

Set the employee code used in your HR or payroll system, so claims match the right person. Owners can also set it in the portal.

curl -X PATCH https://waazn.com/api/v1/employees/31 \
  -H "Authorization: Bearer wz_live_..." -H "Content-Type: application/json" \
  -d '{"employee_code":"E-1042"}'

Company card spending

GET/transactions

Payments and refunds on registered company cards, as reported by the card holders' phones (needs spending). Query: since (payment time), limit, after.

Claims and card payments never overlap: a payment made on a registered company card can't be submitted as a claim.

Webhooks

Add an endpoint in Integrations → Webhooks. Pick who it's for (Finance, HR or Other) and the events. A claim can go to both your finance and HR systems: add one endpoint for each.

EventWhen
claim.submittedAn employee sent a claim.
claim.receipt_addedA receipt photo was attached or replaced (only while pending).
claim.approvedThe company approved it. The usual trigger to book it.
claim.rejectedThe company rejected it (decision_note says why).
claim.withdrawnThe employee withdrew a pending claim.
claim.exportedA connected system reported it as booked, paid or failed.
transaction.createdA new company card payment or refund arrived.
POST https://your-system.example.com/waazn
Content-Type: application/json
Waazn-Event: claim.approved
Waazn-Event-Id: evt_5f0c2b1e9a7d4c3b8e6f1a20
Waazn-Delivery: 7781
Waazn-Signature: t=1791842400,v1=6b1f0c…

{
  "id": "evt_5f0c2b1e9a7d4c3b8e6f1a20",
  "type": "claim.approved",
  "created": "2026-10-13T08:40:00.000Z",
  "destination": "finance",
  "business": { "id": 7, "name": "Acme Trading" },
  "data": { "claim": { "id": 412, "object": "claim", "status": "approved", ... } }
}

Reply with any 2xx within 10 seconds. Anything else is retried after 1, 5, 30, 120, 360 and 1440 minutes, then marked failed. You can retry it from the portal. The same Waazn-Event-Id can arrive more than once, so ignore ids you've already processed.

Verify signatures

Each endpoint has its own secret (whsec_…), shown when you create it. Compute HMAC-SHA256 of t + "." + raw body with the secret and compare it with v1. Reject requests older than 5 minutes.

// Node.js
const crypto = require("crypto");
function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map(p => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const want = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return v1.length === want.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(want));
}
# Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300: return False
    want = hmac.new(secret.encode(), f'{parts["t"]}.'.encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(want, parts["v1"])

The claim object

{
  "id": 412,
  "object": "claim",
  "status": "approved",                    // pending | approved | rejected | withdrawn
  "employee": { "id": 31, "name": "Sara Ali", "email": "sara@acme.com", "employee_code": "E-1042" },
  "merchant": "Careem",
  "category": "transport",
  "category_name": "Transport",
  "gl_account": "6100",                    // from Integrations → Expense account codes, or null
  "note": "Airport to client meeting",     // the employee's reason
  "amount": { "value": 86.00, "currency": "AED" },           // as paid
  "amount_company": { "value": 8.805, "currency": "BHD" },   // converted at the day's rate
  "card_last4": "8812",
  "bank": "BBK",
  "paid_at": "2026-10-11T22:38:00.000Z",
  "submitted_at": "2026-10-11T22:40:12.000Z",
  "decided_at": "2026-10-12T06:02:44.000Z",
  "decided_by": "finance@acme.com",
  "decision_note": null,
  "receipt": { "url": "https://waazn.com/api/v1/receipts/412?exp=…&sig=…", "expires_at": "…", "content_type": "image/jpeg" },
  "receipt_scan": { "status": "done", "merchant": "Cafe Bahrain", "date": "2026-10-12", "total": 10.5, "currency": "BHD", "vat_amount": 0.955, "vat_rate": 10,
    "vat_number": "200012345600002", "invoice_number": "INV-77", "check": "match", "difference": 0 },
  "exports": [ { "destination": "finance", "system": "zoho_books", "external_id": "EXP-000981", "status": "exported", "note": null, "at": "…" } ]
}

The transaction object has id, type (payment or refund), merchant, amount, amount_company, card {label, bank, last4}, holder, at, over_limit and note.

Connecting your system

SystemHow
Zoho Books, Zoho Expense, Zoho PeoplePoint a webhook at a Zoho Flow or Deluge function, then create an expense (or a bill for reimbursement) with the claim's amount, gl_account and receipt URL. Report back with external_id.
QuickBooks Online, Xero, OdooThe same pattern through their APIs, Zapier or Make: claim.approved → create expense or bill → POST /exports.
Sage 50 (Peachtree) and other desktop softwareDesktop software can't receive webhooks. Poll GET /claims?status=approved&not_exported_to=finance on a schedule from a small connector, or import the CSV from the portal (Expense claims → Download CSV) through Sage's import, then report back.
HR and payroll (Bayzat, ZenHR, Zoho People, others)Add a second webhook with destination HR and set employee_code for each employee, so reimbursements land on the right payslip. Report paid when it's paid.
Need a ready-made connector for your system? Email hello@waazn.com and tell us which one.

Errors and limits

HTTP 401
{ "error": { "code": "unauthorized", "message": "This API key isn't valid or was revoked." } }
StatusMeaning
400 / 422A field is missing or wrong. The message says which.
401Missing, wrong or revoked key.
403The key lacks the permission, or the company's account is paused.
404Not found in this company.
409Not allowed in the claim's current state (for example exporting a pending claim).
429Rate limit: 1,200 requests per hour per key.