# Webhooks

Register, verify, secure, test, and recover webhook deliveries.

Webhook endpoints live under `/v2/webhooks/{teamId}`. Register a public HTTPS receiver:

```bash
curl --request POST \
  "https://developers.micro.so/v2/webhooks/${MICRO_TEAM_ID}" \
  --header "content-type: application/json" \
  --header "x-api-key: ${MICRO_API_KEY}" \
  --header "idempotency-key: $(uuidgen)" \
  --data '{"name":"Production events","url":"https://example.com/micro/webhooks","enabled":true}'
```

The response shows the `whsec_…` signing secret once. Store it immediately. Creation starts an asynchronous verification handshake and can succeed before `verified` becomes `true`.

## Complete the handshake

Micro sends a GET request with `micro_hook_mode=subscribe`, a one-time `micro_hook_challenge`, and `micro_hook_token`. Verify the token created for this webhook, then respond 200 with the challenge verbatim as plain text. Poll the webhook until `verified` is true or call its verify endpoint to retry.

## Verify every delivery

Deliveries use `x-micro-signature: t=<unix-seconds>,v1=<hex-hmac>`. The digest is HMAC-SHA256 over `<timestamp>.<raw-body>` using the `whsec_…` secret. Preserve the raw request bytes; parsing and re-serializing JSON can change the digest.

```js
import crypto from "node:crypto";

export function verifyMicroSignature(rawBody, header, secret) {
  if (!Buffer.isBuffer(rawBody) || typeof header !== "string" || typeof secret !== "string") {
    return false;
  }
  const values = new Map(header.split(",").map((part) => {
    const index = part.indexOf("=");
    if (index < 1) return ["", ""];
    return [part.slice(0, index).trim(), part.slice(index + 1).trim()];
  }));
  const timestamp = values.get("t");
  const supplied = values.get("v1");
  if (!timestamp || !/^[0-9]+$/.test(timestamp) || !supplied || !/^[0-9a-f]{64}$/i.test(supplied)) {
    return false;
  }

  const timestampSeconds = Number(timestamp);
  const age = Math.abs(Date.now() / 1000 - timestampSeconds);
  if (!Number.isFinite(age) || age > 300) return false;

  const expected = crypto.createHmac("sha256", secret)
    .update(Buffer.concat([Buffer.from(`${timestamp}.`, "utf8"), rawBody]))
    .digest();
  const received = Buffer.from(supplied, "hex");
  return received.length === expected.length && crypto.timingSafeEqual(expected, received);
}
```

Reject invalid or stale signatures before parsing the event. Deduplicate work by `x-micro-delivery-id`, because a delivery can be attempted more than once. `x-micro-webhook-id` identifies the subscription and `x-micro-event` identifies the event name.

## Acknowledge and recover

Return a 2xx response quickly, then process asynchronously. Non-2xx responses and timeouts are retried, and every attempt appears in the delivery log. Use the team or per-webhook delivery endpoints to find failures and inspect their attempt history. A webhook must be enabled and verified before the ping endpoint can send `webhook.test`.

The event catalog is not yet published. Accept unknown fields and event names without failing the receiver; route only events your integration understands.
