0Pricing
AI Agents · درس

ربط الوكلاء بـ Webhooks

استقبال أحداث webhook وتشغيل مسارات عمل الوكيل استجابةً لها

ربط الوكلاء بـ Webhooks درس مجاني في AI Agents على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Agents، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Agents 4 دروس في المجموع.

ما هو Webhook؟

إن Webhook هو استدعاء HTTP عكسي. عند وقوع حدث في خدمة خارجية، ترسل تلك الخدمة طلب POST إلى نقطة النهاية الخاصة بك مع بيانات الحدث. يعالج وكيلك الحمولة وينفّذ الإجراء المناسب.

تعتمد Webhooks على الدفع، أي تصل الأحداث عند وقوعها، بخلاف الاستطلاع الذي تتحقق فيه من البيانات بشكل متكرر.

نقطة نهاية Webhook في FastAPI

تسهّل FastAPI إنشاء مستقبِل Webhook. عرّف مسار POST، وحلّل نص JSON، ثم سلّمه إلى منطق الوكيل.

from fastapi import FastAPI, Request
from pydantic import BaseModel

app = FastAPI()

class WebhookPayload(BaseModel):
    event: str
    data: dict

@app.post('/webhook')
async def receive_webhook(payload: WebhookPayload):
    print(f'Received event: {payload.event}')
    print(f'Data: {payload.data}')
    
    # Route to the right agent handler
    if payload.event == 'email.received':
        await handle_email_event(payload.data)
    elif payload.event == 'file.uploaded':
        await handle_file_event(payload.data)
    
    return {'status': 'accepted'}

async def handle_email_event(data: dict):
    print(f'Processing email from: {data.get("from")}')

async def handle_file_event(data: dict):
    print(f'Processing file: {data.get("filename")}')

التحقق من توقيع Webhook

تحقق دائمًا من أن طلبات Webhook واردة من المرسل المتوقع. توقّع معظم الخدمات حمولاتها باستخدام HMAC-SHA256 وباستخدام سر مشترك. ارفض الطلبات ذات التوقيعات غير الصالحة.

import hmac
import hashlib
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()
WEBHOOK_SECRET = 'your-webhook-secret-here'

def verify_signature(payload_bytes: bytes, signature_header: str) -> bool:
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        payload_bytes,
        hashlib.sha256
    ).hexdigest()
    received = signature_header.replace('sha256=', '')
    return hmac.compare_digest(expected, received)

@app.post('/webhook/verified')
async def verified_webhook(request: Request):
    payload_bytes = await request.body()
    signature = request.headers.get('X-Signature', '')
    
    if not verify_signature(payload_bytes, signature):
        raise HTTPException(status_code=401, detail='Invalid signature')
    
    # Safe to process
    import json
    data = json.loads(payload_bytes)
    return {'status': 'verified', 'event': data.get('event')}

مفاتيح عدم تكرار التنفيذ

تعيد الخدمات الخارجية غالبًا إرسال عمليات تسليم Webhook الفاشلة. مفتاح عدم تكرار التنفيذ هو معرّف فريد يُرسل مع كل حدث. خزّن المفاتيح التي عولجت وتجاهل التكرارات.

from fastapi import FastAPI, Request, HTTPException
import redis
import json

app = FastAPI()
r = redis.Redis(host='localhost', port=6379, decode_responses=True)

@app.post('/webhook/idempotent')
async def idempotent_webhook(request: Request):
    payload = await request.json()
    
    # Extract idempotency key from header or payload
    idempotency_key = request.headers.get('Idempotency-Key') or payload.get('event_id')
    
    if not idempotency_key:
        raise HTTPException(status_code=400, detail='Missing idempotency key')
    
    redis_key = f'webhook:processed:{idempotency_key}'
    
    # Check if already processed
    if r.exists(redis_key):
        print(f'Duplicate event {idempotency_key}, skipping')
        return {'status': 'duplicate', 'idempotency_key': idempotency_key}
    
    # Process event
    # ... agent logic here ...
    
    # Mark as processed (expire after 24h)
    r.setex(redis_key, 86400, '1')
    return {'status': 'processed', 'idempotency_key': idempotency_key}

استراتيجية إزالة تكرار عمليات إعادة المحاولة

إضافةً إلى مفاتيح عدم تكرار التنفيذ، ضع في اعتبارك نوافذ إزالة التكرار. إذا تلقيت محتوى الحدث نفسه خلال فترة قصيرة، فمن المرجح أن تكون الرسالة إعادة محاولة. قارن تجزئات الأحداث لاكتشاف عمليات إعادة المحاولة وإسقاطها.

import hashlib
import json
from datetime import datetime

# In-memory store; use Redis in production
recent_hashes = {}
DEDUP_WINDOW_SECONDS = 300  # 5 minutes

def is_duplicate(payload: dict) -> bool:
    # Hash the event content
    content = json.dumps(payload, sort_keys=True)
    event_hash = hashlib.md5(content.encode()).hexdigest()
    
    now = datetime.utcnow().timestamp()
    
    # Clean up old entries
    expired = [h for h, ts in recent_hashes.items() if now - ts > DEDUP_WINDOW_SECONDS]
    for h in expired:
        del recent_hashes[h]
    
    if event_hash in recent_hashes:
        return True
    
    recent_hashes[event_hash] = now
    return False

# Test
payload = {'event': 'payment.completed', 'amount': 100}
print('First:', is_duplicate(payload))   # False
print('Second:', is_duplicate(payload))  # True (duplicate)

تشغيل الوكيل بشكل غير متزامن

ينبغي لمعالجات Webhook أن تستجيب بسرعة (خلال أقل من 5 ثوانٍ) وأن تعالج منطق الوكيل في الخلفية. استخدم BackgroundTasks في FastAPI لتجنب انتهاء المهلة.

from fastapi import FastAPI, BackgroundTasks
import asyncio

app = FastAPI()

async def run_agent_job(event: str, data: dict):
    print(f'Agent starting for event: {event}')
    await asyncio.sleep(2)  # Simulate LLM call
    print(f'Agent finished for event: {event}')

@app.post('/webhook/async')
async def async_webhook(request_data: dict, background_tasks: BackgroundTasks):
    event = request_data.get('event', 'unknown')
    data = request_data.get('data', {})
    
    # Respond immediately
    background_tasks.add_task(run_agent_job, event, data)
    
    return {'status': 'accepted', 'message': 'Processing in background'}

تحليل الحمولات المعقدة

ترسل الخدمات المختلفة حمولات بأشكال مختلفة. اكتب دوال تحليل مخصصة لكل خدمة حتى يتلقى وكيلك دائمًا كائن حدث موحّدًا.

from dataclasses import dataclass
from typing import Optional

@dataclass
class NormalizedEvent:
    event_type: str
    source: str
    resource_id: str
    metadata: dict

def parse_github_webhook(payload: dict) -> NormalizedEvent:
    return NormalizedEvent(
        event_type='github.' + payload.get('action', 'unknown'),
        source='github',
        resource_id=str(payload.get('repository', {}).get('id', '')),
        metadata={
            'repo': payload.get('repository', {}).get('full_name'),
            'sender': payload.get('sender', {}).get('login')
        }
    )

def parse_stripe_webhook(payload: dict) -> NormalizedEvent:
    return NormalizedEvent(
        event_type=payload.get('type', 'unknown'),
        source='stripe',
        resource_id=payload.get('id', ''),
        metadata={'amount': payload.get('data', {}).get('object', {}).get('amount')}
    )

# Usage
github_payload = {'action': 'opened', 'repository': {'id': 123, 'full_name': 'user/repo'}, 'sender': {'login': 'alice'}}
event = parse_github_webhook(github_payload)
print(event)

أهمية رموز استجابة Webhook

أعِد رمز HTTP الصحيح. يخبر الرمز 2xx المرسلَ بأنه قُبل Webhook. ويعني الرمز 4xx وجود خطأ من جانب العميل (حمولة غير صحيحة). أما الرمز 5xx أو انتهاء المهلة فيؤدي إلى إعادة المرسل المحاولة.

  • 200: قُبل وعولج
  • 202: قُبل للمعالجة غير المتزامنة
  • 400: طلب غير صالح (حقول مفقودة)
  • 401: توقيع غير صالح
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse

app = FastAPI()

@app.post('/webhook/proper-responses')
async def proper_webhook(request: Request):
    try:
        payload = await request.json()
    except Exception:
        raise HTTPException(status_code=400, detail='Invalid JSON body')
    
    required_fields = ['event', 'data']
    for field in required_fields:
        if field not in payload:
            raise HTTPException(status_code=400, detail=f'Missing field: {field}')
    
    event = payload['event']
    known_events = ['email.received', 'file.uploaded', 'payment.completed']
    
    if event not in known_events:
        # Acknowledge unknown events gracefully - do not retry
        return JSONResponse(status_code=200, content={'status': 'ignored', 'reason': 'unknown event'})
    
    # Start background processing
    return JSONResponse(status_code=202, content={'status': 'accepted'})

اختبار Webhooks محليًا

استخدم ngrok لإتاحة خادمك المحلي عبر الإنترنت بغرض الاختبار. شغّل ngrok http 8000 للحصول على عنوان URL عام ينشئ نفقًا إلى تطبيق FastAPI المحلي.

# Start your FastAPI app
# uvicorn main:app --reload --port 8000

# In another terminal, start ngrok:
# ngrok http 8000
# You get: https://abc123.ngrok.io

# Now configure your webhook in Stripe/GitHub/etc. to:
# https://abc123.ngrok.io/webhook

# Test with curl:
import subprocess

def test_webhook_locally():
    test_payload = '{"event": "email.received", "data": {"from": "test@example.com"}}'
    # In real usage you would run this in terminal:
    # curl -X POST http://localhost:8000/webhook \
    #   -H 'Content-Type: application/json' \
    #   -d '{"event": "email.received", "data": {"from": "test@example.com"}}'
    print('Test payload:', test_payload)
    print('Send to: http://localhost:8000/webhook')

test_webhook_locally()

تسجيل أحداث Webhook

سجّل كل Webhook وارد مع الطابع الزمني والمصدر ونوع الحدث ونتيجة المعالجة. يُعد مسار التدقيق هذا ضروريًا لتصحيح مشكلات الأحداث الفائتة أو معالجة التكرارات.

import logging
import json
from datetime import datetime
import sys

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s %(levelname)s %(message)s',
    stream=sys.stdout
)
logger = logging.getLogger('webhook')

def log_webhook_event(event_id: str, event_type: str, source: str, status: str, details: dict = None):
    logger.info(json.dumps({
        'timestamp': datetime.utcnow().isoformat(),
        'event_id': event_id,
        'event_type': event_type,
        'source': source,
        'status': status,
        'details': details or {}
    }))

# Usage in webhook handler
log_webhook_event(
    event_id='evt_123',
    event_type='email.received',
    source='gmail',
    status='processed',
    details={'from': 'user@example.com', 'action_taken': 'reply_sent'}
)

تحديد معدل Webhooks الواردة

احمِ نقطة نهاية Webhook من التعرّض للضغط الزائد باستخدام تحديد معدل الطلبات. تضيف مكتبة slowapi تحديد معدل الطلبات إلى FastAPI باستخدام قدر ضئيل من التعليمات البرمجية.

from fastapi import FastAPI, Request
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded

limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

@app.post('/webhook/limited')
@limiter.limit('100/minute')
async def rate_limited_webhook(request: Request):
    payload = await request.json()
    return {'status': 'accepted', 'event': payload.get('event')}

اختبار المعرفة: Webhooks

اختبر مدى فهمك لأفضل ممارسات Webhooks الخاصة بالوكلاء.

Webhooks في بيئة الإنتاج

في بيئة الإنتاج، اجمع كل الأنماط: التحقق من التوقيع، ومفاتيح عدم تكرار التنفيذ، والمعالجة في الخلفية، والتسجيل المنظّم، وتحديد معدل الطلبات. انشر التطبيق خلف وكيل عكسي مثل nginx لإنهاء TLS وتوفير حماية إضافية.

الأسئلة الشائعة

هل درس «ربط الوكلاء بـ Webhooks» مجاني؟

نعم — نص درس «ربط الوكلاء بـ Webhooks» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Agents، انتقل إلى CoddyKit PRO. تتضمن دورة AI Agents 4 دروس في المجموع.

ماذا ستتعلم في «ربط الوكلاء بـ Webhooks»؟

استقبال أحداث webhook وتشغيل مسارات عمل الوكيل استجابةً لها تتمرن على AI Agents مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ AI Agents؟

لا تُشترط خبرة سابقة. AI Agents على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «ربط الوكلاء بـ Webhooks»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس AI Agents هذا؟

نعم. كل درس في AI Agents يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. أنماط وكلاء المحفز والإجراء
  2. ربط الوكلاء بـ Webhooks
  3. الوكلاء المجدولون والقائمون على Cron
  4. بناء خط أنابيب لأتمتة تطبيقات متعددة
← العودة إلى AI Agents