> ## 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.

# Hızlı başlangıç: ilk webhook'unuzu alın

> Webhook Tester, kayıtlı bir webhook uç noktası ve event sink arasında seçim yapın — ardından tünellenmiş bir alıcı kurun, imzayı doğrulayın ve teslimleri konsoldan yeniden oynatın.

# Hızlı başlangıç: ilk webhook'unuzu alın

Orbit olaylarını platformdan dışarı taşıyan üç teslim yüzeyi vardır; bu rehber birini seçmenize yardımcı olur, ardından kayıtlı uç nokta döngüsünü — tünel, kayıt, doğrulama, yeniden oynatma — uçtan uca yürütür.

## Step 1 — Pick a delivery surface

Üç yüzeyi ayıran çizgi nettir: deneme yanılma için Tester, üretim değeri taşıyan her şey için kayıtlı uç nokta, kendi Kafka ya da parti toplama altyapınıza yüksek hacimli akış için event sink.

| 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](https://docs.orbit.devotel.io/api-reference/event-sinks.md) page for the current status.

Bu rehberin geri kalanı kayıtlı uç nokta döngüsünü yürütür. Yalnızca keşfetmek için geldiyseniz [Webhook Tester](https://docs.orbit.devotel.io/guides/webhook-tester.md) sayfasını açın ve bir istek imza kontrolünden geçemediğinde buraya dönün.

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

Yerelde herhangi bir sade HTTP dinleyicisi çalıştırın ve önüne kamuya açık bir URL koyun; bir sonraki adımda kaydedeceğiniz adres, tünelin HTTPS yönlendirme adresidir. Kayıt ve yeniden oynatma boyunca tüneli açık tutun.

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

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

Panoda **Developer → Webhooks → Add endpoint** yolunu izleyin (aynı yere **Settings → Webhooks** altından da ulaşılır). Tünel URL'nizi yapıştırın, bir-iki olay türüne abone olun ve kaydedin. Konsol, açık imza gizli anahtarını (`whsec_...`) tam olarak bir kez gösterir — şimdi bir ortam değişkenine kopyalayın.

Aynı kayıt API veya SDK'ler üzerinden:

```bash curl 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"]
  }'
```

```javascript Node SDK theme={null}
const endpoint = await orbit.webhooks.create({
  url: "https://your-tunnel-url.ngrok.io/webhooks/orbit",
  events: ["message.delivered"],
});
// endpoint.data.secret holds the one-time cleartext secret
```

```python Python raw request theme={null}
# The SDK wrapper covers the core resources; for webhooks, POST the route
# through the client's request() escape hatch:
result = client.request(
    "POST",
    "/webhooks",
    json_body={
        "url": "https://your-tunnel-url.ngrok.io/webhooks/orbit",
        "events": ["message.delivered"],
    },
)
print(result["data"]["id"], "- secret:", result["data"]["secret"])
```

```go Go (escape hatch) theme={null}
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/devotel/orbit-go/orbit"
)

func main() {
    client := orbit.NewClientFromAPIKey(os.Getenv("ORBIT_API_KEY"))

    body := map[string]any{
        "url":    "https://your-tunnel-url.ngrok.io/webhooks/orbit",
        "events": []string{"message.delivered"},
    }
    var resp struct {
        Data struct {
            ID     string `json:"id"`
            Secret string `json:"secret"`
        } `json:"data"`
    }
    if err := client.Request(context.Background(), "POST", "/webhooks", nil, body, &resp, ""); err != nil {
        panic(err)
    }
    fmt.Println(resp.Data.ID, "- secret:", resp.Data.Secret)
}
```

```ruby Ruby (escape hatch) theme={null}
require "orbit_sdk"

client = OrbitSdk::Client.from_env  # reads ORBIT_API_KEY

resp = client.request("POST", "/webhooks", json_body: {
  url: "https://your-tunnel-url.ngrok.io/webhooks/orbit",
  events: ["message.delivered"]
})
puts "#{resp.dig('data', 'id')} - secret: #{resp.dig('data', 'secret')}"
```

```php PHP (escape hatch) theme={null}
<?php
require __DIR__.'/vendor/autoload.php';

use Devotel\Orbit\OrbitClient;

$client = OrbitClient::fromEnv();  // reads ORBIT_API_KEY

$resp = $client->request('POST', '/webhooks', null, [
    'url'    => 'https://your-tunnel-url.ngrok.io/webhooks/orbit',
    'events' => ['message.delivered'],
]);
echo $resp['data']['id'], " - secret: ", $resp['data']['secret'];
```

Go, Ruby ve PHP aynı rotayı her istemcinin tipli olmayan istek yönteminden (`client.Request` / `client.request` / `$client->request`) POST eder — tipli sarmalayıcılar çekirdek kaynakları kapsar, webhook'lar o kaçış yolundan gider.

Yanıt, uç nokta kimliğinizi (`wh_...`) ve bir kez görülen açık gizli anahtarı (`whsec_...`) taşır — şimdi kalıcı olarak saklayın; çünkü uç noktaya sonraki her `GET`, `whsec_********...b7a1` benzeri maskeli bir değer döndürür:

```json theme={null}
{
  "data": {
    "id": "wh_187f5c2e9d3a4f21b9e8d6c04a2f1e7b",
    "url": "https://your-tunnel-url.ngrok.io/webhooks/orbit",
    "events": ["message.delivered"],
    "active": true,
    "secret": "whsec_f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0d1c2"
  },
  "meta": {
    "request_id": "req_01f3b8c4"
  }
}
```

`events` listesini dar tutmak test trafiğini sağlıklı kılar; `*` bir üretim kararıdır, geliştirme kararı değil.

## Step 4 — Receive and verify the signature

Kayıtlı teslimler, `t=<unix>,v1=<hex>` biçiminde bir `X-Orbit-Signature` başlığıyla imzalı gelir. Alıcınız, ham istek baytları üzerinde — bir JSON ara katmanı onlara dokunmadan önce — `HMAC_SHA256(secret, "<t>.<raw body>")` hesaplayıp doğrulamalı ve 5 dakikadan eski zaman damgalarını reddetmelidir. Bunu tek çağrıyla SDK yapar; çevresindeki tesisat (ham gövde, başlık yedeği, hatada `401`) sizin doğru kurmanız gereken kısımdır. Beş dilde tam alıcılar:

```typescript Node.js (Express) theme={null}
import express from 'express';
import { Orbit, OrbitWebhookSignatureError } from '@devotel-orbit/node';

const app = express();

app.post(
  '/webhooks/orbit',
  express.raw({ type: 'application/json' }), // raw bytes, not parsed JSON
  (req, res) => {
    const signature =
      (req.headers['x-orbit-signature'] as string | undefined) ??
      (req.headers['x-devotel-signature'] as string | undefined) ??
      '';
    try {
      const event = Orbit.webhooks.constructEvent(
        req.body.toString('utf8'),
        signature,
        process.env.ORBIT_WEBHOOK_SECRET!,
      );
      switch (event.type) {
        case 'message.delivered':
          // handle the event...
          break;
        default:
          break;
      }
      res.status(200).json({ received: true });
    } catch (err) {
      if (err instanceof OrbitWebhookSignatureError) {
        return res.status(401).json({ error: err.message });
      }
      throw err;
    }
  },
);
```

```python Python (Flask) theme={null}
from flask import Flask, request, jsonify
from orbit_sdk import verify_webhook
from orbit_sdk.errors import OrbitWebhookSignatureError

app = Flask(__name__)

@app.route("/webhooks/orbit", methods=["POST"])
def handle_webhook():
    signature = (
        request.headers.get("X-Orbit-Signature")
        or request.headers.get("X-Devotel-Signature")
        or ""
    )
    try:
        event = verify_webhook(
            payload=request.get_data(),  # raw bytes — don't parse first
            signature=signature,
            secret="whsec_your_secret",
        )
    except OrbitWebhookSignatureError as exc:
        return jsonify({"error": str(exc)}), 401
    # handle event ...
    return jsonify({"received": True}), 200
```

```go Go (net/http) theme={null}
package main

import (
    "io"
    "net/http"
    "time"

    orbit "github.com/devotel/orbit-go/orbit"
)

func handler(w http.ResponseWriter, r *http.Request) {
    rawBody, err := io.ReadAll(r.Body) // raw bytes; reuse for verify + JSON
    if err != nil {
        http.Error(w, "read error", http.StatusBadRequest)
        return
    }
    signature := r.Header.Get("X-Orbit-Signature")
    if signature == "" {
        signature = r.Header.Get("X-Devotel-Signature")
    }
    event, err := orbit.VerifyWebhook(
        rawBody,
        signature,
        "whsec_your_secret",
        0,            // default tolerance
        time.Time{},  // now
    )
    if err != nil {
        http.Error(w, `{"error":"invalid signature"}`, http.StatusUnauthorized)
        return
    }
    _ = event // handle the event...
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"received":true}`))
}

func main() {
    http.HandleFunc("/webhooks/orbit", handler)
    http.ListenAndServe(":8080", nil)
}
```

```ruby Ruby (Sinatra) theme={null}
require "sinatra"
require "orbit_sdk"

post "/webhooks/orbit" do
  signature = request.env["HTTP_X_ORBIT_SIGNATURE"] ||
              request.env["HTTP_X_DEVOTEL_SIGNATURE"] || ""
  begin
    event = OrbitSdk::Webhooks.verify(
      payload: request.body.read, # raw body
      signature: signature,
      secret: ENV["ORBIT_WEBHOOK_SECRET"]
    )
    # handle event ...
    status 200
    { received: true }.to_json
  rescue OrbitSdk::OrbitWebhookSignatureError => e
    status 401
    { error: e.message }.to_json
  end
end
```

```php PHP theme={null}
<?php

use Devotel\Orbit\Webhooks;
use Devotel\Orbit\Errors\OrbitWebhookSignatureError;

$signature = $_SERVER['HTTP_X_ORBIT_SIGNATURE']
    ?? $_SERVER['HTTP_X_DEVOTEL_SIGNATURE']
    ?? '';

try {
    $event = Webhooks::verify(
        payload: file_get_contents('php://input'), // raw body
        signature: $signature,
        secret: getenv('ORBIT_WEBHOOK_SECRET'),
    );
} catch (OrbitWebhookSignatureError $e) {
    http_response_code(401);
    echo json_encode(['error' => $e->getMessage()]);
    exit;
}

// handle $event ...
http_response_code(200);
echo json_encode(['received' => true]);
```

Her işleyici, herhangi bir JSON ayrıştırıcı dokunmadan önce ham gövdeyi okur, `X-Orbit-Signature`'dan miras `X-Devotel-Signature`'a düşer, tek SDK çağrısıyla doğrular ve başarısızlıkta `401` yanıtı verir — böylece yeniden denemeler asla sahte trafiği onaylamaz. Java ve C# alıcıları ile gizli anahtar rotasyonu ve hata giderme [Verify webhook signatures](https://docs.orbit.devotel.io/guides/verify-webhook-signatures.md) sayfasındadır.

30 saniyelik teslim penceresi içinde `2xx` dönün ve zarf `id`'sine göre tekilleştirin — yeniden denemeler ve yeniden oynatmalar aynı id'yi tekrar teslim eder. Tam sözleşme (yeniden deneme takvimi, dead-letter kuyruğu, kesin-ölü durumlar) [Webhooks overview](https://docs.orbit.devotel.io/webhooks/overview.md) sayfasındadır; [Build your first webhook receiver](https://docs.orbit.devotel.io/webhooks/first-receiver.md) iki dilde tam bir gerçekleme yürütür, [Webhook security](https://docs.orbit.devotel.io/webhooks/security.md) yedi dilde doğrulayıcılar sunar.

## Step 5 — Replay deliveries from the console

Her teslim denemesi **Developer → Webhooks → Events** altında incelenebilir — bir satırı seçin ve sunucunuzun döndürdüğü yükü ile yanıtı görün. Başarısız bir teslim, teslim satırındaki **Replay** ile yeniden gönderilebilir; böylece alıcınızı düzeltip aynı imzalı olayı yeni trafik tetiklemeden yeniden ateşlersiniz.

Yeniden deneme takvimini tüketen teslimler, **Developer → Webhooks → Dead-letter queue** altındaki dead-letter kuyruğuna düşer ve 7 gün boyunca yeniden oynatılabilir. Tek satır yerine biriken bir kuyruğu yeniden ateşleyecekseniz o ekrandaki toplu yeniden oynatma akışını kullanın. İki yol da [Inspecting deliveries](https://docs.orbit.devotel.io/webhooks/inspecting-deliveries.md) sayfasında kapsanır.

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

Alıcı başına kayıtlı bir uç nokta, artık öyle olmayana dek doğru biçimdir — geçiş sinyalleri somuttur:

* **Akış aşağınız Kafka ise.** `kafka` sink'ini broker'larınıza doğrultun ve her olay grubu için HTTP alıcıları işletmeyi bırakın.
* **Olay başına POST değil parti istiyorsanız.** `http_batch` sink'i, yapılandırılabilir parti boyutunda bir JSON dizisi boşaltır.
* **Her şeyi yakalamak için `*`'a abone oluyorsanız.** Tüm taksonomiyi taşıyan tek sink, bir wildcard uç nokta filosundan iyidir.

Sink'i konsolda **Developer → Event Sinks** altında ya da [Event Sinks API](https://docs.orbit.devotel.io/api-reference/event-sinks.md) üzerinden yapılandırın:

```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"
  }'
```

Kimlik bilgisi yalnız yazılır — okumalar gizli anahtarı asla döndürmez, bir `credentials_configured` boolean'ı döndürür. Kayıt biçimi webhook zarfıyla bayt-özdeştir; bu yüzden bugün yazdığınız bir tüketici, sink teslimi canlıya geçtiğinde çalışmaya devam eder; o zamana dek kalıcı trafiği webhook'larda tutun ve sink'i yalnız yapılandırmayı hazırlamak için kullanın. Tam fan-out modeli [Webhook fan-out: one event, many destinations](https://docs.orbit.devotel.io/concepts/webhook-fan-out-and-event-sinks.md) sayfasındadır.

## See also

* [Test webhooks with the Webhook Tester](https://docs.orbit.devotel.io/guides/webhook-tester.md) — yineleme için imzasız yapay gönderimler
* [Build your first webhook receiver](https://docs.orbit.devotel.io/webhooks/first-receiver.md) — tam alıcı anlatımı
* [Build a durable webhook consumer](https://docs.orbit.devotel.io/guides/webhook-consumer.md) — kuyruklu işlemeli üretim düzeyi alıcı
* [Event Sinks API](https://docs.orbit.devotel.io/api-reference/event-sinks.md) — Kafka ve HTTP-batch sink yapılandırması
* [Webhook fan-out](https://docs.orbit.devotel.io/concepts/webhook-fan-out-and-event-sinks.md) — bir olayın her aboneliğe ulaşma biçimi


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.