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

# بدء سريع: استقبل أول webhook

> اختر بين Webhook Tester ونقطة webhook مسجلة وevent sink — ثم شغّل مستقبِلًا عبر نفق، وتحقق من التوقيع، وأعد إرسال حالات التسليم من وحدة التحكم.

# بدء سريع: استقبل أول webhook

ثلاثة أسطح تسليم تُخرج أحداث Orbit من المنصة؛ يساعدك هذا الدليل على اختيار أحدها، ثم يمشي حلقة النقطة المسجلة كاملة — نفق، تسجيل، تحقق، إعادة إرسال — من الطرف إلى الطرف.

## Step 1 — Pick a delivery surface

الخط الفاصل بين الأسطح الثلاثة واضح: الـ Tester للتجربة، والنقطة المسجلة لكل ما يحمل قيمة إنتاجية، والـ event sink لبث أحجام كبيرة إلى بنية Kafka أو تجميع دفعات تديرها بنفسك.

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

يمشي بقية هذا الدليل حلقة النقطة المسجلة. إذا كنت هنا للاستكشاف فقط، افتح [Webhook Tester](https://docs.orbit.devotel.io/guides/webhook-tester.md) وَعُد عندما يفشل طلب في فحوص التوقيع.

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

شغّل أي مستمع HTTP مجرد محليًا وضع أمامه عنوان URL عامًا: العنوان الذي تسجله في الخطوة التالية هو عنوان إعادة التوجيه HTTPS الخاص بالنفق. أبقِ النفق يعمل طوال العمل في التسجيل وإعادة الإرسال.

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

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

في لوحة التحكم انتقل إلى **Developer → Webhooks → Add endpoint** (ويمكن الوصول إليها أيضًا تحت **Settings → Webhooks**). الصق عنوان النفق، واشترك في نوع حدث أو اثنين، واحفظ. تعرض وحدة التحكم سرّ التوقيع الصريح (`whsec_...`) مرة واحدة بالضبط — انسخه الآن إلى متغير بيئة.

التسجيل نفسه عبر الواجهة البرمجية أو الـ SDKs:

```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 وPHP المسار نفسه عبر دالة الطلب غير المكتوبة في كل عميل (`client.Request` / `client.request` / `$client->request`) — الأغلفة المكتوبة تغطي الموارد الأساسية، بينما تمر الـ webhooks عبر ذلك المهرب.

تحمل الاستجابة معرف نقطتك (`wh_...`) والسرّ الصريح الذي يُرى مرة واحدة (`whsec_...`) — احفظه الآن، لأن كل `GET` لاحق على النقطة يعيد قيمة مقنعة مثل `whsec_********...b7a1`:

```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` الضيقة تُبقي حركة الاختبار معقولة؛ أما `*` فقرار إنتاجي لا قرار تطويري.

## Step 4 — Receive and verify the signature

تصل حالات التسليم المسجلة موقعة بترويسة `X-Orbit-Signature` بصيغة `t=<unix>,v1=<hex>`. على مستقبلك التحقق من `HMAC_SHA256(secret, "<t>.<raw body>")` على بايتات الطلب الخام — قبل أن يمسّها أي وسيط JSON — ورفض الطوابع الزمنية الأقدم من 5 دقائق. يقوم الـ SDK بذلك في استدعاء واحد؛ أما التوصيلات المحيطة (الجسم الخام، والتراجع في الترويسة، و`401` عند الفشل) فهي الجزء الذي عليك إتقانه. مستقبلات كاملة بخمس لغات:

```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]);
```

يقرأ كل معالج الجسم الخام قبل أن يلمسه أي محلل JSON، ويتراجع من `X-Orbit-Signature` إلى `X-Devotel-Signature` القديم، ويتحقق باستدعاء SDK واحد، ويجيب بـ`401` عند الفشل حتى لا تقرّ إعادات المحاولة حركة مزيفة أبدًا. مستقبلات Java وC#، إضافة إلى تدوير الأسرار واستكشاف الأخطاء، في [Verify webhook signatures](https://docs.orbit.devotel.io/guides/verify-webhook-signatures.md).

أعِد `2xx` خلال نافذة التسليم البالغة 30 ثانية وأزل التكرار اعتمادًا على `id` في الظرف — تعيد الإعادات وعمليات إعادة الإرسال الـ id نفسه. العقد الكامل (جدول الإعادات، وطابور الرسائل الميتة، والحالات المؤكدة الموت) في [Webhooks overview](https://docs.orbit.devotel.io/webhooks/overview.md)؛ ويحتوي [Build your first webhook receiver](https://docs.orbit.devotel.io/webhooks/first-receiver.md) تنفيذًا كاملًا بلغتين، فيما يطرح [Webhook security](https://docs.orbit.devotel.io/webhooks/security.md) أدوات تحقق بسبع لغات.

## Step 5 — Replay deliveries from the console

يمكن فحص كل محاولة تسليم تحت **Developer → Webhooks → Events** — اختر صفًا لرؤية الحمولة والاستجابة التي أعادها خادمك. يمكن إعادة إرسال التسليم الفاشل بزر **Replay** على صف التسليم، فتصلح مستقبلك وتعيد إطلاق الحدث الموقع نفسه من دون إثارة حركة جديدة.

تهبط حالات التسليم التي تستنفد جدول الإعادات في طابور الرسائل الميتة تحت **Developer → Webhooks → Dead-letter queue**، ويمكن إعادة تشغيلها لمدة 7 أيام. وعندما يكون لديك تراكم تريد إعادة إطلاقه لا صفًا واحدًا، استخدم تدفق إعادة الإرسال الجماعي في تلك الشاشة. يغطي المساران معًا في [Inspecting deliveries](https://docs.orbit.devotel.io/webhooks/inspecting-deliveries.md).

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

نقطة مسجلة لكل مستقبِل هي الشكل الصحيح إلى أن تعود كذلك — وإشارات الانتقال ملموسة:

* **جهة المصب لديك Kafka.** وجّه sink من نوع `kafka` إلى وسطائك وتوقف عن تشغيل مستقبلات HTTP لكل مجموعة أحداث.
* **تريد دفعات لا طلبات POST لكل حدث.** يفرغ sink من نوع `http_batch` مصفوفة JSON من الأظرفة بحجم دفعة قابل للضبط.
* **تشترك بـ`*` لالتقاط كل شيء.** sink واحد يحمل التصنيف الكامل خير من أسطول نقاط wildcard.

هيّئ الـ sink في **Developer → Event Sinks** في وحدة التحكم، أو عبر [Event Sinks API](https://docs.orbit.devotel.io/api-reference/event-sinks.md):

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

بيانات الاعتماد للكتابة فقط — تعيد القراءات قيمة منطقية `credentials_configured` ولا تعيد السرّ أبدًا. شكل السجل مطابق بايتيًا لظرف الـ webhook، لذا يبقى المستهلك الذي تكتبه اليوم يعمل عندما يصبح تسليم الـ sink حيًا؛ وحتى ذلك الحين أبقِ الحركة الدائمة على الـ webhooks واستخدم الـ sink فقط لتجهيز الإعداد. نموذج الـ fan-out الكامل في [Webhook fan-out: one event, many destinations](https://docs.orbit.devotel.io/concepts/webhook-fan-out-and-event-sinks.md).

## See also

* [Test webhooks with the Webhook Tester](https://docs.orbit.devotel.io/guides/webhook-tester.md) — إرسالات تركيبية غير موقعة للتكرار والتجربة
* [Build your first webhook receiver](https://docs.orbit.devotel.io/webhooks/first-receiver.md) — شرح كامل للمستقبِل
* [Build a durable webhook consumer](https://docs.orbit.devotel.io/guides/webhook-consumer.md) — مستقبِل بمستوى إنتاجي مع معالجة عبر طابور
* [Event Sinks API](https://docs.orbit.devotel.io/api-reference/event-sinks.md) — إعداد sinks من نوع Kafka وHTTP-batch
* [Webhook fan-out](https://docs.orbit.devotel.io/concepts/webhook-fan-out-and-event-sinks.md) — كيف يبلغ الحدث الواحد كل اشتراك


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