> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart: receive your first webhook

> Pick between the Webhook Tester, a registered webhook endpoint, and an event sink — then stand up a tunneled receiver, verify the signature, and replay deliveries from the console.

# Quickstart: receive your first webhook

Three delivery surfaces take Orbit events off the platform; this guide helps you pick one, then walks the full registered-endpoint loop — tunnel, register, verify, replay — end to end.

## Step 1 — Pick a delivery surface

| Surface                         | What it is                                                                                                                                                             | Pick it when                                                                                |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| **Webhook Tester**              | Dashboard tool (**Developer → Webhook Tester**) that fires one synthetic, unsigned event at any HTTPS URL and shows the status, headers, and body your server returned | Iterating on payload shape or response handling before you register a real endpoint         |
| **Registered webhook endpoint** | A durable subscription: signed, retried, deduplicated, and dead-lettered, pointing at your HTTPS receiver                                                              | Anything that matters in production — receiver code in this guide is built for this surface |
| **Event sink**                  | Streams every subscribed event to your own Kafka topic or batch HTTP collector instead of a receiver per endpoint                                                      | High-volume consumption into infrastructure you already run                                 |

Start with the Tester when you're experimenting. Graduate to a registered endpoint as soon as the integration is real — the tester cannot sign requests, so it never exercises your verification logic. Choose an event sink when per-endpoint receivers are the wrong shape — Kafka, batch, or many event types over one destination.

<Warning>
  Event-sink **delivery is not live yet**: the config API (`GET`/`PATCH /api/v1/developer/event-sinks/{kind}` for `kafka` and `http_batch`) stores configs today, but nothing flows through a sink until the delivery worker ships. Until then, a registered webhook endpoint is the only durable consumer surface. Check the warning block on the [Event Sinks API](/api-reference/event-sinks) page for the current status.
</Warning>

The rest of this guide walks the registered-endpoint loop. If you're only here to explore, open the [Webhook Tester](/guides/webhook-tester) and come back when a request fails signature checks.

## Step 2 — Expose a local endpoint with a tunnel

Run any bare HTTP listener locally and put a public URL in front of it:

```bash theme={null}
# any tunnel works — ngrok, localtunnel, cloudflared
ngrok http 3000
```

Copy the HTTPS forwarding URL — that is the address you register in the next step. Keep the tunnel running while you work through registration and replay.

## Step 3 — Register the endpoint in the console (or over the API)

In the dashboard, go to **Developer → Webhooks → Add endpoint** (also reachable under **Settings → Webhooks**). Paste your tunnel URL, subscribe to one or two event types, and save. The console shows the cleartext signing secret (`whsec_...`) exactly once — copy it into an environment variable now.

The same registration over the API:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/webhooks \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-tunnel-url.ngrok.io/webhooks/orbit",
    "events": ["message.delivered"]
  }'
```

The response carries your endpoint id (`wh_...`) and the one-time cleartext secret. Narrow `events` lists keep test traffic sane; `*` is a production decision, not a development one.

## Step 4 — Receive and verify the signature

Registered deliveries arrive signed with an `X-Orbit-Signature` header in `t=<unix>,v1=<hex>` format. Your receiver must verify `HMAC_SHA256(secret, "<t>.<raw body>")` over the raw request bytes — before any JSON middleware parses them — and reject timestamps older than 5 minutes. A minimal Node check:

```javascript theme={null}
import crypto from "node:crypto";

function verifySignature(rawBody, header, secret) {
  if (!header) return false;
  let t = null;
  const v1s = [];
  for (const part of header.split(",")) {
    const [key, value] = part.trim().split("=");
    if (key === "t") t = value;
    else if (key === "v1") v1s.push(value);
  }
  if (!t || v1s.length === 0) return false;

  const ageSec = Math.floor(Date.now() / 1000) - Number(t);
  if (!Number.isFinite(ageSec) || ageSec < 0 || ageSec > 5 * 60) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody.toString("utf8")}`, "utf8")
    .digest("hex");

  return v1s.some((candidate) => {
    const a = Buffer.from(candidate, "hex");
    const b = Buffer.from(expected, "hex");
    return a.length === b.length && crypto.timingSafeEqual(a, b);
  });
}
```

Return `2xx` within the 30-second delivery window and dedupe on the envelope `id` — retries and replays re-deliver the same id. The full contract (retry schedule, dead-letter queue, proven-dead statuses) is on [Webhooks overview](/webhooks/overview); [Build your first webhook receiver](/webhooks/first-receiver) has a complete implementation in two languages, and [Webhook security](/webhooks/security) ships verifiers in seven.

## Step 5 — Replay deliveries from the console

Every delivery attempt is inspectable in **Developer → Webhooks → Events** — pick a row to see the payload and the response your server returned. A failed delivery can be re-sent with **Replay** on the delivery row, so you can fix your receiver and re-fire the same signed event without triggering new traffic.

Deliveries that exhaust the retry schedule land in the dead-letter queue under **Developer → Webhooks → Dead-letter queue**, replayable for 7 days. When you have a backlog to re-fire rather than one row, use the bulk-replay flow on that screen. Both paths are covered in [Inspecting deliveries](/webhooks/inspecting-deliveries).

## Step 6 — Move to an event sink when you outgrow HTTP receivers

A registered endpoint per receiver is the right shape until it isn't — the move-off signals are concrete:

* **Your downstream is Kafka.** Point the `kafka` sink at your brokers and stop running HTTP receivers for every event group.
* **You want batches, not per-event POSTs.** The `http_batch` sink flushes a JSON array of envelopes at a configurable batch size.
* **You're subscribing `*` to catch everything.** One sink carrying the whole taxonomy beats a fleet of wildcard endpoints.

Configure a sink in **Developer → Event Sinks** in the console, or over the [Event Sinks API](/api-reference/event-sinks):

```bash theme={null}
curl -X PATCH https://api.orbit.devotel.io/api/v1/developer/event-sinks/kafka \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "events": ["message.*", "call.completed"],
    "destination": {
      "kind": "kafka",
      "topic": "orbit.events",
      "partition_key_path": "data.contact_id"
    },
    "brokers": ["broker-1.example.com:9092"],
    "sasl_mechanism": "scram-sha-512",
    "sasl_username": "orbit-producer",
    "credentials": "the-sasl-password"
  }'
```

The credential is write-only — reads return a `credentials_configured` boolean, never the secret. The record shape is byte-identical to the webhook envelope, so a consumer you write today keeps working when sink delivery goes live; until then, keep the durable traffic on webhooks and use the sink only to stage the config. The full fan-out model is on [Webhook fan-out: one event, many destinations](/concepts/webhook-fan-out-and-event-sinks).

## See also

* [Test webhooks with the Webhook Tester](/guides/webhook-tester) — unsigned synthetic sends for iteration
* [Build your first webhook receiver](/webhooks/first-receiver) — full receiver walkthrough in Node and Python
* [Build a durable webhook consumer](/guides/webhook-consumer) — production-grade receiver with queued processing
* [Event Sinks API](/api-reference/event-sinks) — Kafka and HTTP-batch sink configuration
* [Webhook fan-out](/concepts/webhook-fan-out-and-event-sinks) — how one event reaches every subscription
