# KoboToolbox Webhooks

KoboToolbox (field data collection) calls its webhooks **REST Services**. A REST Service posts each
new submission of a project to a URL you choose, as JSON or XML. Requests are **not signed** and
have **no event type**: every request is a new submission (edits send nothing). Your endpoint
authenticates the request with HTTP Basic auth, using the username and password you entered on
the REST Service.

> A static shared credential authenticates the sender, not the body, and gives no replay
> protection (no signature, no timestamp). Use HTTPS and rotate the credential if it leaks.

## When to Use This Skill

* How do I receive KoboToolbox REST Service webhooks?
* How do I verify the KoboToolbox `Authorization: Basic` header?
* Does KoboToolbox sign webhook payloads? (No: use Basic auth)
* How do I handle a KoboToolbox JSON or XML submission?
* Why does my KoboToolbox endpoint return 401?

## Verification (core)

```javascript
const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
const safeEqual = (a, b) => crypto.timingSafeEqual(
  crypto.createHash('sha256').update(a).digest(), crypto.createHash('sha256').update(b).digest());

function verifyKoboRequest(headers, user = process.env.KOBOTOOLBOX_WEBHOOK_USERNAME,
                           pass = process.env.KOBOTOOLBOX_WEBHOOK_PASSWORD) {
  if (!user || !pass) return false;                          // never accept when unconfigured
  const m = /^basic +([A-Za-z0-9+/]+=*)\s*$/i.exec(headers.authorization || '');
  if (!m) return false;
  const creds = Buffer.from(m[1], 'base64');
  const i = creds.indexOf(0x3a);                             // split on the FIRST colon
  if (i < 0) return false;
  const userOk = safeEqual(creds.subarray(0, i), Buffer.from(user));
  const passOk = safeEqual(creds.subarray(i + 1), Buffer.from(pass));
  return userOk && passOk;                                   // evaluate both, no short-circuit
}

```

* Header: `Authorization: Basic base64(username:password)`; scheme is case-insensitive.
* Python: `hmac.compare_digest` on **bytes** (encode both sides; `str` raises on non-ASCII).
* Return `401` for a missing, malformed or mismatched header, `200` once handled.
* Read the body raw (`express.raw({ type: '*/*' })`) and authenticate before parsing. The body may
  be JSON or XML; don't reject non-JSON.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/kobotoolbox-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/kobotoolbox-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/kobotoolbox-webhooks/examples/fastapi/).

## Payload

There is no event type. The body is the submission:

| Field | Meaning |
| --- | --- |
| `_id` | Submission ID |
| `_uuid` | Submission UUID (use with `_id` to deduplicate retries) |
| `_submission_time` | When the submission was received |
| `_xform_id_string` | Form ID |
| question fields | Your form's answers, keyed by question name |

The REST Service can also send XML, only a subset of fields, or (JSON only) a custom wrapper
around the submission. KoboToolbox documents 3 retries (after 60, 600 and 6,000 seconds), though the open-source
`kpi` sender declares a backoff capped at 1,200 seconds and retries only `502`, `503`, `504` and read timeouts,
so a `401` or `500` fails at once. Return `503` for a temporary problem.

## Environment Variables

```bash
KOBOTOOLBOX_WEBHOOK_USERNAME=your-username
KOBOTOOLBOX_WEBHOOK_PASSWORD=your-long-random-password

```

## Local Development

KoboToolbox can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 kobotoolbox --path /webhooks/kobotoolbox

```

Use the printed URL as the REST Service endpoint.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/kobotoolbox-webhooks/references/overview.md) - What REST Services are, when they fire, payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/kobotoolbox-webhooks/references/setup.md) - Register a REST Service and set the credentials
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/kobotoolbox-webhooks/references/verification.md) - Basic auth check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/kobotoolbox-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests