Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.neuraldraft.io/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks are the alternative to polling. Subscribe an HTTPS URL on your side to one or more event topics; we’ll POST a signed JSON payload every time something matters. Use them to rebuild a static site, fan out to Slack/Discord, sync orders to your warehouse, or refresh a CDN cache.

Setup in 60 seconds

1

Stand up an endpoint

Any HTTPS URL that responds 2xx within 30 seconds. Keep it cheap; do the heavy work asynchronously after acknowledging.
// app/api/neural-draft/route.ts (Next.js App Router)
import { NextResponse } from "next/server";
import { verifyNeuralDraftSignature } from "@/lib/neural-draft";

export async function POST(req: Request) {
  const raw = await req.text();
  const signature = req.headers.get("x-neural-draft-signature") ?? "";
  const ok = verifyNeuralDraftSignature(raw, signature, process.env.NEURAL_DRAFT_WEBHOOK_SECRET!);
  if (!ok) return new NextResponse("invalid signature", { status: 401 });

  const event = JSON.parse(raw);
  // Acknowledge fast, do work async.
  queueMicrotask(() => handleEvent(event));
  return NextResponse.json({ received: true });
}
2

Subscribe

Create the endpoint via API or dashboard. The response includes a signing_secret — store it; it’s shown only once.
curl -X POST https://api.neuraldraft.io/v1/webhook-endpoints \
  -H "Authorization: Bearer $NEURAL_DRAFT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/api/neural-draft",
    "events": ["order.paid", "order.refunded", "blog_post.published"]
  }'
{
  "id": "whe_2NgcaXxFqLPo",
  "url": "https://example.com/api/neural-draft",
  "events": ["order.paid", "order.refunded", "blog_post.published"],
  "is_active": true,
  "signing_secret": "whsec_OmtQ9X5gTzUv7sR1kBhJnAeYpV2cDfWiM3oP",
  "created_at": "2026-04-19T10:14:22Z"
}
3

Verify and act

On every delivery, recompute the HMAC and compare it constant-time. Reject requests with a stale timestamp (we recommend a 5-minute window).

Event topics

TopicFires when
blog_post.publishedA post transitions to status=published, manually or via scheduler.
blog_post.translatedA post translation finishes (per language).
social_post.publishedA scheduled or on-demand publish succeeded on at least one platform.
social_post.failedAll platforms in a post returned an error.
order.createdA new Order row is created (manual or Stripe Checkout).
order.paidAn Order transitions to payment_status=paid.
order.fulfilledAn Order transitions to status=delivered.
order.cancelledAn Order transitions to status=cancelled.
order.refundedA full or partial refund completes.
booking.confirmedA booking is confirmed (auto or manual).
booking.cancelledA booking is cancelled (admin or customer).
booking.completedA booking is marked complete after the appointment.
content.changedA translation key value changes — useful for SSG rebuild triggers.
image.generatedAn async image generation job completes.
connect.account_updatedStripe Connect status changes (onboarding, requirements).
A complete reference with payload schemas is on the API reference.

Delivery shape

POST /api/neural-draft HTTP/2
Host: example.com
Content-Type: application/json
User-Agent: Neural-Draft-Webhooks/1.0
X-Neural-Draft-Event: order.paid
X-Neural-Draft-Delivery: whd_2Nh4PqRsTuVw
X-Neural-Draft-Signature: t=1745007660,v1=4f3c1e8b6a... (hex)

{
  "id": "evt_2Nh4PqRsTuVw",
  "type": "order.paid",
  "created": 1745007660,
  "data": { /* same shape as the GET resource */ }
}

Verifying the signature

The signature is a hex-encoded HMAC-SHA256 of the raw body (do not parse and re-stringify) using your signing_secret. The header is t=<unix>,v1=<hex>; future signature versions will append additional algorithms (e.g. v2=).
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyNeuralDraftSignature(
  body: string,
  header: string,
  secret: string,
  toleranceSeconds = 300
): boolean {
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.split("=") as [string, string])
  );
  const t = Number(parts.t);
  const v1 = parts.v1;
  if (!Number.isFinite(t) || !v1) return false;

  // Reject ancient deliveries — protects against replay.
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - t) > toleranceSeconds) return false;

  const signed = `${t}.${body}`;
  const expected = createHmac("sha256", secret).update(signed).digest("hex");

  // Constant-time compare; both sides must be the same length.
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(v1, "hex");
  if (a.length !== b.length) return false;
  return timingSafeEqual(a, b);
}
Always verify against the raw request body — if you parse JSON first and re-serialize, key ordering changes will break the HMAC. In Express, that means express.raw() on the route, not express.json(). In Next.js App Router, use await req.text() (not await req.json()).

Retry behavior

If your endpoint doesn’t respond 2xx within 30 seconds, we retry with exponential backoff: 30 s, 2 m, 10 m, 1 h, 6 h. After 5 failed attempts the delivery is marked failed and visible in the dashboard.
AttemptDelay since previous
1(immediate)
230 seconds
32 minutes
410 minutes
51 hour
66 hours
You can manually replay any past delivery from the dashboard or via the API:
curl -X POST https://api.neuraldraft.io/v1/webhook-endpoints/whe_.../deliveries/whd_.../redeliver \
  -H "Authorization: Bearer $NEURAL_DRAFT_API_KEY"
The data payload is always the resource as it stood at delivery time (not the resource as it stands now). A redeliver replays the original payload.

Best practices

Return 2xx as soon as you’ve persisted the raw payload (or queued a job). Don’t run business logic in the request handler — anything over a few seconds risks a retry that double-processes.
Treat X-Neural-Draft-Delivery as the dedupe key. Index it; reject (and 2xx) duplicates. Retries happen — they’re a feature, not a bug.
Reject deliveries with timestamps more than 5 minutes off. The verifier helpers above do this for you.
Rotate the signing secret quarterly, or instantly if you suspect a leak. The dashboard supports a transition window — both old and new secrets accepted for up to 24 hours.
Subscribe a webhook.delivery_failed event to your alerting; or watch the last_delivery_status field on the endpoint resource. A failed state for more than an hour is your cue to investigate.
A single endpoint subscribed to many topics is fine; the X-Neural-Draft-Event header tells you which one. But splitting concerns by URL (orders, content, bookings) makes failure isolation cleaner.

Local development

Use ngrok, Cloudflare Tunnel or Tailscale Funnel to forward your localhost to a public URL. Subscribe a test-mode endpoint with that URL, and trigger events from the dashboard’s “send test event” button. The Inspector in the dashboard shows every delivery, signature, response code, and replay button.

What “billed” means

Each delivery costs 0.02 credits. Retries count — that’s why fast, reliable acknowledgement saves you money. We cap retries at 5 to keep this predictable.