Start your launch Sign in
APIs 2 min read

API webhooks

Work appears mid-day by design: second comments unlock six hours after the first, and scout finds land whenever a scan returns. A signed POST tells you the moment it does.

Last updated July 24, 2026

Polling a fleet is either late or wasteful, because new work does not arrive on your schedule: the warmer’s mid-day comment unlock and the scout’s scan results both land whenever they land. The webhook is a push that says “this account has work now”, and nothing more; your script then pulls the details over the API it already uses.

01 · SetupSetup

On the API & Webhooks page (account menu), add one https:// endpoint for your account. Saving it mints a signing secret (nk_whsec_...) that you can reveal and copy there any time. From the same card you can pause and resume deliveries, rotate the secret, and send a webhook.test event to prove the wiring end to end. Every attempt, real or test, lands in the delivery log with its status, so “did it send?” is never a mystery.

02 · EventsThe events

Two real events exist, one per engine, both meaning “new tasks are ready to pull”:

  • warmup.tasks_ready: the morning batch for a warm-up, and again mid-day when a held second comment unlocks.
  • scout.tasks_ready: a scan just added conversations to a scout’s inbox.
warmup.tasks_ready
{
	"event": "warmup.tasks_ready",
	"warmup_id": "3f2b7c1e-9d5a-4f42-8f6e-2a91d0c47b13",
	"task_count": 2,
	"at": "2026-07-26T08:00:14.000Z"
}

The scout event mirrors it with scout_id; webhook.test carries just event and at. On receipt, fetch the actual work: GET /v1/tasks?warmup_id=...&status=ready (see the API task loop).

03 · VerificationVerify the signature

Every delivery carries X-Nilkick-Signature, an HMAC-SHA256 of the exact body bytes using your signing secret, formatted sha256=<hex>. Verify it before trusting the event, or anyone who learns your endpoint can spoof “you have work” and wake your fleet:

Verifying the signature
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyNilkick(rawBody, header, secret) {
	const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
	return (
		typeof header === 'string' &&
		header.length === expected.length &&
		timingSafeEqual(Buffer.from(header), Buffer.from(expected))
	);
}

Compute it over the raw request body, not a re-serialized copy of the parsed JSON: any framework that hands you parsed objects first must also expose the original bytes.

04 · ReliabilityDeliveries are sent once

No retries, by design

A delivery is attempted once, with a ten second timeout, and never retried. If your endpoint happens to be down at that moment, the event is gone (the attempt is still visible in the delivery log). Treat webhooks as an accelerator, not as the source of truth.

The safety net is one reconcile poll per account per day: a plain GET /v1/tasks?status=ready at a fixed hour catches anything a missed event would have told you about, and turns a lost delivery into a delay instead of a silently missed day. On a 28-day warm-up curriculum, that one call a day is the difference between “late” and “gone”.


FAQ

Common questions

One endpoint per account today. If several systems need the signal, receive it once and fan out on your side; the payload is small enough to forward untouched.