Run payroll with no code
For a business that runs payroll from a spreadsheet, or wants Zapier/Make/n8n to trigger it, without writing an integration or minting an API key.
This is not the merchant API-key surface the rest of these docs cover — there’s no
Authorization: Bearer sxm_... header here, no scopes. A payroll connector is its own thing:
a webhook URL and a signing secret, both created from the business dashboard, that your own
no-code automation POSTs signed rows to.
Why this is separate from the API
The Payroll API (/v1/api/payroll/*) is for a business with its own
engineers — VoxHR and My-Watchman both integrate against it directly. A connector exists for
everyone else: a business owner who has a Google Sheet, or a Zapier account, and no developer.
Creating a connector is the one interactive step (PIN-approved, funding wallet + spending caps set
once); every trigger after that runs headlessly, bounded by those caps — nobody has to click
“approve” each payday.
1. Create a connector
From the business dashboard: Settings → Developers → Payroll connectors → Add connector. Name it, pick USD or LRD, set a per-run cap and a daily cap, and confirm with your PIN.
You’ll see, once:
- A trigger URL —
https://api.soxara.com/v1/connectors/payroll/{connector_id}/trigger - A signing secret — store it in whatever automation tool you’re using (Zapier’s a secret
storage step, Google Apps Script’s
PropertiesService, an n8n credential)
Neither is shown again. If you lose the secret, revoke the connector and create a new one.
2. Sign and send a trigger
Every trigger is a POST to your connector’s URL with a JSON body and a signature header. The
signature scheme is exactly Soxara’s own outbound webhook scheme
(Signature verification), just reversed — you’re the one signing this time:
X-Connector-Signature: t=<unix seconds>,v1=<hex hmac-sha256>
signed payload = "{timestamp}." + raw_json_bodyBody shape:
{
"idempotency_key": "payroll-2026-09-30", // your own key — a retry with the same one is a no-op
"note": "September payroll", // optional
"payments": [
{ "payee_phone": "+231770000001", "payee_label": "Adama Smith", "amount_cents": 150000 },
{ "payee_phone": "+231770000002", "payee_label": "John Boakai", "amount_cents": 120000, "external_ref": "row-2" }
]
}Node example (works as-is inside a Google Apps Script or a small Zapier “Code by Zapier” step):
const crypto = require('crypto');
function triggerPayroll(secret, connectorUrl, payload) {
const body = JSON.stringify(payload);
const t = Math.floor(Date.now() / 1000);
const sig = crypto.createHmac('sha256', secret).update(`${t}.${body}`).digest('hex');
return fetch(connectorUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Connector-Signature': `t=${t},v1=${sig}`,
},
body,
});
}What happens
The response comes back synchronously with the run’s status:
{
"success": true,
"data": {
"id": "…",
"status": "completed", // completed | partial | failed | rejected
"payroll_run_id": "…",
"total_cents": 270000,
"received_at": "…"
}
}completed— every payee was paid.partial— some payees were paid; others need attention (usually a phone number that isn’t on Soxara yet) — same per-line detail as the Payroll API.failed— nothing was paid.rejected— nothing ran at all: bad signature, a paused/revoked connector, or the trigger’s total exceeded the connector’s per-run or daily cap. Fix the trigger (or the connector’s caps from the dashboard) and resend with a freshidempotency_key.
A payee_phone that doesn’t match an existing Soxara user is never sent an invite — it’s just
marked needs_attention, the same posture as every other phone-resolution path in Soxara.
Pausing and revoking
Pause from the dashboard stops triggers from running without deleting anything — resume later and it picks back up. Revoke is permanent; the URL stops accepting requests immediately. Neither needs a PIN — only creating a connector (raising its authority) does.
Retrying safely
Reuse the same idempotency_key for a genuine retry (your automation crashed before it saw the
response, a network blip, etc.) — a replay of one already processed returns the original result
without running payroll twice. Change the key only when the payment rows actually changed.