# إشعارات webhook

> استقبل إشعارا موقعا عند اكتمال التفريغ أو فشله، وتحقق من توقيعه قبل معالجته.

Canonical: https://transcript1.com/ar/docs/webhooks
Language: ar
Last updated: 2026-10-14

إذا أردت معرفة موعد انتهاء التفريغ دون الاستعلام المتكرر، أضف `webhook_url` عند إنشائه. نرسل إلى هذا الرابط طلب `POST` يحمل توقيعا عندما يكتمل التفريغ أو يفشل.

هذه الميزة متاحة عبر REST API فقط؛ لا تقبل أدوات [خادم MCP](https://transcript1.com/ar/docs/mcp) معامل webhook.

## أضف رابط استقبال الإشعارات

عند إرسال رابط فيديو مدعوم أو حجز رفع على أجزاء، ضع `webhook_url` في JSON المرسل إلى `POST /v1/transcriptions`. وإذا رفعت الملف بطلب واحد، ضعه في حقل من حقول النموذج:

```json
{
  "platform": "youtube",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "webhook_url": "https://example.com/hooks/transcript1"
}
```

استخدم رابط HTTPS دون اسم مستخدم أو كلمة مرور. يجب أن يشير إلى عنوان IP عام؛ لا نقبل عناوين الشبكات الخاصة أو loopback أو link-local أو أي عنوان غير عام. إذا لم يستوف الرابط هذه الشروط، تحصل على `422 invalid_request`. نفحص العنوان عند إنشاء التفريغ وقبل كل محاولة إرسال.

لا ترسل `webhook_url` مع `reuse_recent=true`؛ لا يمكن استخدامهما معا.

## متى يصلك الإشعار؟

| `type` | سبب الإرسال |
|---|---|
| `transcription.completed` | اكتمل التفريغ وأصبحت حالته `completed`. |
| `transcription.failed` | فشل التفريغ وأصبحت حالته `failed`. تجد `code` و`message` في `data.error`. |

إذا ألغيت التفريغ، لا نرسل webhook.

## محتوى الإشعار

يصلك body بصيغة JSON، كما في هذا المثال:

```json
{
  "type": "transcription.completed",
  "created_at": "2026-10-13T09:30:04.000Z",
  "data": {
    "id": "tr_8fK2Lm0PqRsTuVwXyZaBcD",
    "object": "transcription",
    "status": "completed",
    "transcript_source": "youtube_captions",
    "billed_minutes": 4,
    "error": null
  }
}
```

يبين `created_at` وقت إنشاء الحدث. يحتوي `data` على كائن التفريغ دون حقل `transcript`؛ عرضنا نسخة مختصرة منه أعلاه. لجلب النص المفرغ، استخدم `id` الموجود في `data` مع `GET /v1/transcriptions/:id`.

تتضمن الترويسات `Content-Type: application/json`، و`User-Agent` بقيمة `transcript1-webhooks/1.0`، إضافة إلى ترويسة التوقيع التالية.

## تحقق من التوقيع قبل قراءة JSON

نرسل مع كل إشعار ترويسة `X-Transcript1-Signature` بهذه الصيغة:

```text
X-Transcript1-Signature: t=<unix seconds>,v1=<hex digest>
```

- `t` وقت توقيع الطلب، بصيغة Unix وبالثواني.
- `v1` توقيع HMAC-SHA256 بترميز hex. احسبه للسلسلة `"<t>.<raw body>"`، أي قيمة `t` ثم نقطة ثم body كما وصل تماما. استخدم سر التوقيع كاملا كمفتاح، بما فيه البادئة `whsec_`.

ابدأ بالبايتات الأصلية للطلب، قبل تحويل JSON إلى كائن. إعادة تحويل الكائن إلى JSON قد تغير المسافات أو ترتيب المفاتيح، وعندها لن يطابق التوقيع. استخدم مقارنة بزمن ثابت، وتحقق أيضا من قرب `t` من الوقت الحالي. الأمثلة التالية تقبل فارقا لا يتجاوز 5 دقائق.

### Node.js

```javascript
import { Buffer } from 'node:buffer';
import crypto from 'node:crypto';

// header: X-Transcript1-Signature, rawBody: the request body exactly as received,
// secret: your full signing secret including the whsec_ prefix.
export const verifySignature = (secret, header, rawBody, nowSeconds = Math.floor(Date.now() / 1000)) => {
  const fields = Object.fromEntries(header.split(',').map(part => part.trim().split('=')));
  const t = Number(fields.t);

  if (!Number.isInteger(t) || !fields.v1 || Math.abs(nowSeconds - t) > 300) {
    return false;
  }

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

  return fields.v1.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(fields.v1), Buffer.from(expected));
};
```

في Express، اقرأ body كنص كما وصل، دون تحليل JSON مسبقا:

```javascript
import process from 'node:process';
import express from 'express';
import { verifySignature } from './verify-signature.js';

const app = express();

app.post('/hooks/transcript1', express.text({ type: 'application/json' }), (request, response) => {
  const header = request.get('X-Transcript1-Signature') ?? '';

  if (!verifySignature(process.env.TRANSCRIPT1_WEBHOOK_SECRET, header, request.body)) {
    response.sendStatus(400);
    return;
  }

  const { type, data } = JSON.parse(request.body);
  console.log(type, data.id, data.status);
  response.sendStatus(200);
});
```

### Python

```python
import hashlib
import hmac
import time


def verify_signature(secret, header, raw_body, now=None):
    # header: the X-Transcript1-Signature header, raw_body: the request body exactly as received,
    # secret: your full signing secret including the whsec_ prefix.
    fields = dict(part.strip().split("=", 1) for part in header.split(",") if "=" in part)
    try:
        t = int(fields["t"])
    except (KeyError, ValueError):
        return False

    now = int(time.time()) if now is None else now
    if abs(now - t) > 300:
        return False

    expected = hmac.new(secret.encode(), f"{t}.{raw_body}".encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(fields.get("v1", ""), expected)
```

في Flask، استخدم `request.get_data(as_text=True)` للحصول على `raw_body`.

## أين تجد سر التوقيع؟

افتح صفحة [«Webhooks»](https://transcript1.com/dashboard/webhooks) في `/dashboard/webhooks` لعرض السر أو استبداله. بادئته `whsec_`، وهو سر واحد لجميع إشعارات التفريغ في فريقك. عند استبداله، يصبح السر الجديد ساريا فورا وتستخدمه كل الإشعارات التي نرسلها بعد ذلك.

## الاستقبال وإعادة المحاولة

- أعد أي رمز `2xx` لتأكيد وصول الإشعار.
- نعتبر المحاولة فاشلة عند وصول رمز آخر، أو حدوث خطأ اتصال، أو عدم الرد خلال 10 ثوان.
- نكرر الإرسال بعد الفشل حتى 6 محاولات إجمالا. تتضاعف فترات الانتظار بين المحاولات إلى 2 ثم 4 ثم 8 ثم 16 ثم 32 دقيقة؛ فتأتي المحاولة الأخيرة بعد نحو ساعة من الأولى.
- استخدم الرابط النهائي مباشرة؛ لا يتبع transcript1 إعادة التوجيه، ويعامل `3xx` كمحاولة فاشلة.
- قد يصلك الإشعار نفسه أكثر من مرة، مثلا إذا أجاب خادمك بعد المهلة. اعتمد على `data.id` و`type` لاكتشاف التكرار وتجاهله.
