Developer API and Webhooks

Connect Sparko to Your Own Systems

Sparko gives your engineering team two building blocks for custom integrations: a read-only public API for pulling people, time-and-leave, and recruiting data on demand, and webhooks that push a signed notification to your endpoint the moment key events happen. Both are managed from Admin Settings and are designed to be safe by default: keys and secrets are shown once, every request is tenant-isolated, and outbound webhooks are cryptographically signed.


Overview

What you can build

  • Pull data on a schedule — sync employees into a data warehouse, feed a BI dashboard, or reconcile a downstream system nightly using the public API.
  • React to events in real time — kick off a provisioning workflow when someone is hired, notify payroll when a departure is recorded, or update an applicant-tracking mirror when a candidate changes stage.

Two surfaces, two auth models

Surface Direction How it authenticates
Public API Your system pulls from Sparko API key in an X-API-Key header
Webhooks Sparko pushes to your system HMAC-SHA256 signature you verify with a signing secret

Availability

The developer API and webhooks are a paid capability. They are available on paid plans; on the free plan the endpoints return 403. If you downgrade, existing keys stop authenticating, but you can still sign in to audit and revoke them.

Note: The public API is intentionally read-only. There is no write scope — the API can never create, edit, or delete records in your Sparko tenant.


Part 1 — The Public API

Base URL

https://api.sparko.app/api/integrate/v1

Creating an API key

API keys are created and managed under Admin Settings → Security & Compliance → API Keys.

  1. Go to Admin Settings → Security & Compliance → API Keys
  2. Click Create API Key
  3. Give the key a descriptive name (for example, "Data warehouse sync" or "Zapier integration")
  4. Choose the scopes the key should carry (see below) — pick only what the integration needs
  5. Click Create key
  6. Copy the key immediately. It starts with spk_live_ and is shown exactly once. Sparko stores only a one-way hash, so if you lose the key you must create a new one.

Security: Treat the key like a password. Store it in your secrets manager, never in source control or a browser. Only the key's short prefix (for example spk_live_ab12cd…) is ever shown again, so you can recognize a key in the list without exposing the secret.

Authenticating requests

Send the key in an X-API-Key header:

curl https://api.sparko.app/api/integrate/v1/employees \
  -H "X-API-Key: spk_live_your_key_here"

An Authorization: Bearer <key> header works too. Every request is resolved to your company only — the tenant is derived from the key itself, never from anything in the request, so a key can only ever read your own data.

Any authentication problem (missing key, revoked key, expired key) returns a generic 401 Unauthorized with no detail about which key or why. A valid key that lacks the scope an endpoint requires returns 403 Forbidden.

Scopes

A key carries one or more read-only scopes. Each endpoint requires a specific scope, so a key scoped to people.read can list employees but cannot touch recruiting data.

Scope Grants read access to
people.read Employees and departments
timeleave.read Leave requests and leave balances
recruiting.read Jobs, applications, and candidate names

Endpoints

All endpoints are GET only. List endpoints accept skip and limit query parameters for pagination (limit defaults to 50, maximum 200).

Method Path Scope Returns
GET /employees people.read A page of employees
GET /employees/{employee_id} people.read One employee
GET /leave-requests timeleave.read A page of leave requests
GET /leave-balances timeleave.read A page of leave balances
GET /jobs recruiting.read A page of job postings
GET /jobs/{job_id} recruiting.read One job posting (with full description)
GET /applications recruiting.read A page of applications (candidate name + stage)

What the API returns (and what it does not)

Responses are deliberately trimmed to non-sensitive fields. The API surfaces things like job title, department, employment status, dates, leave balances, and application stage. It never returns deep personal data — no email, phone number, date of birth, national ID, home address, salary, or demographic information. Employee and candidate names are the only decrypted personal fields, and only on the endpoints whose scope you granted.

Example — list employees:

curl "https://api.sparko.app/api/integrate/v1/employees?limit=2" \
  -H "X-API-Key: spk_live_your_key_here"
{
  "items": [
    {
      "employee_id": "emp_01H...",
      "employee_number": "E-1042",
      "first_name": "Emily",
      "last_name": "Thomas",
      "job_title": "Senior Engineer",
      "department": "Engineering",
      "employment_status": "active",
      "manager_id": "emp_01G...",
      "hire_date": "2023-04-01",
      "termination_date": null
    }
  ],
  "skip": 0,
  "limit": 2
}

Rate limits

The public API is limited to 300 requests per minute per key. Space out bulk syncs and use pagination rather than hammering an endpoint.

Rotating and revoking keys

  • Rotate: Create a new key, update your integration to use it, then revoke the old one. There is no in-place edit — rotation is always "create new, retire old."
  • Revoke: Click Revoke next to a key in the list. Revocation is immediate — the next request with that key gets a 401.

Part 2 — Webhooks

Webhooks let Sparko notify your systems the instant something happens, so you do not have to poll the API. When a subscribed event occurs, Sparko sends a signed POST to the URL you configured.

The events you can subscribe to

A subscription listens for one or more of these five events:

Event Fires when
employee.hired A new employee is hired
employee.terminated An employee is terminated / offboarded
leave.approved A leave request is approved
application.stage_changed A candidate moves to a new pipeline stage
offer.accepted A candidate accepts an offer

Creating a subscription

Webhooks are managed under Admin Settings → Security & Compliance → Webhooks.

  1. Go to Admin Settings → Security & Compliance → Webhooks
  2. Click Add webhook
  3. Enter your endpoint URL (must be https:// and publicly reachable)
  4. Optionally add a short description
  5. Choose which events to receive
  6. Click Create webhook
  7. Copy the signing secret immediately. It starts with whsec_ and is shown exactly once. You will use it to verify that incoming webhooks genuinely came from Sparko.

URL safety: Sparko validates your endpoint URL when you save it and re-checks the resolved address again at delivery time. Endpoints that point at private, internal, or loopback addresses are rejected — webhooks can only be delivered to genuinely public destinations.

The webhook payload

Sparko sends a JSON envelope with a stable shape:

{
  "id": "del_01H...",
  "event": "leave.approved",
  "created_at": "2026-07-27T14:03:11+00:00",
  "data": {
    "request_id": "req_01H...",
    "employee_id": "emp_01H..."
  }
}

Each delivery also carries these headers:

Header Purpose
X-Sparko-Event The event name (for example leave.approved)
X-Sparko-Delivery A unique delivery ID, useful for de-duplication
X-Sparko-Signature The signature you verify (see below)

Your endpoint should respond with any 2xx status code to acknowledge receipt. Anything else is treated as a failed delivery and retried.

Verifying the signature

Every webhook is signed so you can confirm it came from Sparko and was not tampered with or replayed. The X-Sparko-Signature header looks like this:

X-Sparko-Signature: t=1753624991,v1=4f2c...9ab
  • t is the Unix timestamp when we signed the request.
  • v1 is an HMAC-SHA256 hex digest computed over the string "{t}." + raw_request_body, keyed with your subscription's signing secret.

To verify, recompute the HMAC yourself and compare:

import hashlib
import hmac
import time

def verify(secret: str, header: str, raw_body: bytes, tolerance_seconds: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    timestamp, provided = parts.get("t"), parts.get("v1")
    if not timestamp or not provided:
        return False

    # Reject stale deliveries to defend against replays.
    if abs(int(time.time()) - int(timestamp)) > tolerance_seconds:
        return False

    signed = f"{timestamp}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, provided)

Two rules for a correct implementation:

  1. Sign the raw bytes. Compute the HMAC over the exact request body as received, before any JSON re-serialization. Re-encoding can change bytes and break the signature.
  2. Enforce the timestamp window. Reject signatures whose t is more than a few minutes old (Sparko's own tolerance is 5 minutes) so an intercepted request cannot be replayed later.

Use a constant-time comparison (like hmac.compare_digest above) rather than ==.

Testing an endpoint

After you add a webhook, click Test on the subscription. Sparko sends a one-off signed test delivery so you can confirm your endpoint receives the request and verifies the signature correctly — before any real event depends on it.

Delivery, retries, and the dead-letter state

Sparko attempts each delivery immediately when the event fires. If your endpoint does not return a 2xx, the delivery is retried with exponential backoff:

Attempt Waits before retrying
2nd ~1 minute
3rd ~5 minutes
4th ~15 minutes
5th ~1 hour
(final) ~6 hours

After 5 failed attempts, the delivery is marked dead-letter and no longer retried automatically.

To inspect what happened, click Deliveries on a subscription. Each row shows the event, its status (delivered, failed, or dead_letter), the last HTTP status code your endpoint returned, the attempt count, and when it was sent. You can Redeliver any past delivery to send it again as a fresh attempt — handy after you fix an outage on your side.

Pausing and deleting

  • Pause / Resume a subscription to temporarily stop deliveries without losing its configuration or signing secret.
  • Delete a subscription to remove it and its delivery history. This cannot be undone.

Who can manage the API and webhooks

Access is controlled by permissions, so you decide which admins can see and change these settings:

Permission Allows
admin.api_keys.view See the list of API keys (never the secrets)
admin.api_keys.manage Create and revoke API keys
admin.webhooks.view See webhook subscriptions and their delivery history
admin.webhooks.manage Create, edit, test, redeliver, pause, and delete webhooks

These are typically held by IT Admin and Super Admin roles. See Roles and Permissions for how to assign them.


Best Practices

Least privilege: Scope each API key to only the data its integration needs ✅ One key per integration: So you can revoke one without breaking the others ✅ Store secrets safely: Keep API keys and signing secrets in a secrets manager, never in code or config committed to source control ✅ Always verify signatures: Reject any webhook whose signature or timestamp does not check out ✅ Make handlers idempotent: Use X-Sparko-Delivery to de-duplicate, since a retried delivery may arrive more than once ✅ Respond fast: Acknowledge with a 2xx quickly and do heavy work asynchronously, so a slow handler is not treated as a failure ✅ Rotate keys periodically: Create a new key, migrate, then revoke the old one


Troubleshooting

Q: My API request returns 401 Unauthorized

  • The key is missing, revoked, expired, or mistyped. Confirm you are sending it in the X-API-Key header and that the key is still Active in the list. If in doubt, create a fresh key.

Q: My API request returns 403 Forbidden

  • Either your plan does not include API access, or the key is missing the scope the endpoint requires. Check the key's scopes in the list and create a new key with the right scope if needed.

Q: My API request returns 429 Too Many Requests

  • You have exceeded 300 requests per minute. Slow down and paginate with skip / limit instead of many rapid calls.

Q: My webhook signature check keeps failing

  • Verify you are computing the HMAC over the raw request body and the string "{timestamp}." + body, using the exact signing secret shown when you created the subscription. Re-serializing the JSON before hashing is the most common cause.

Q: A webhook shows as dead_letter

  • Your endpoint failed all retry attempts. Fix the endpoint, then use Redeliver to resend that delivery.

Q: I lost my API key or signing secret

  • Secrets are shown only once and cannot be recovered. Create a new key (or a new webhook subscription) and update your integration.

Need Help?


Applies To: Admins on paid Sparko plans Feature Tier: Developer API and webhooks are a paid capability