0Pricing
AI Agents · บทเรียน

การเชื่อมต่อตัวแทนกับเว็บฮุก

รับเหตุการณ์เว็บฮุกและเรียกใช้เวิร์กโฟลว์ตัวแทนเพื่อตอบสนอง

การเชื่อมต่อตัวแทนกับเว็บฮุก เป็นบทเรียน AI Agents ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน AI Agents และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส AI Agents มีบทเรียนทั้งหมด 4 บทเรียน

เว็บฮุกคืออะไร

เว็บฮุกคือการเรียกกลับผ่าน HTTP เมื่อเกิดเหตุการณ์ในบริการภายนอก บริการนั้นจะส่งคำขอ POST ที่มีข้อมูลเหตุการณ์มายังปลายทางของคุณ เอเจนต์จะประมวลผลข้อมูลที่ส่งมาและดำเนินการ

เว็บฮุกทำงานแบบพุช (เหตุการณ์มาถึงทันทีที่เกิดขึ้น) ต่างจากการตรวจสอบเป็นระยะ (คุณเรียกตรวจสอบซ้ำ ๆ)

ปลายทางเว็บฮุกของ FastAPI

FastAPI ช่วยให้สร้างตัวรับเว็บฮุกได้ง่าย กำหนดเส้นทาง 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")}')

การตรวจสอบลายเซ็นเว็บฮุก

ตรวจสอบเสมอว่าคำขอเว็บฮุกมาจากผู้ส่งที่คาดไว้ บริการส่วนใหญ่ลงลายเซ็นข้อมูลที่ส่งมาด้วย 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')}

คีย์ป้องกันการดำเนินการซ้ำ

บริการภายนอกมักลองส่งเว็บฮุกที่ล้มเหลวอีกครั้ง คีย์ป้องกันการดำเนินการซ้ำคือ ID เฉพาะที่ส่งมาพร้อมกับแต่ละเหตุการณ์ ให้จัดเก็บคีย์ที่ประมวลผลแล้วและข้ามรายการซ้ำ

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)

การเรียกใช้เอเจนต์แบบอะซิงโครนัส

ตัวจัดการเว็บฮุกควรตอบกลับอย่างรวดเร็ว (ภายใน 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)

รหัสตอบกลับของเว็บฮุกมีความสำคัญ

ส่งคืนสถานะ HTTP ที่ถูกต้อง รหัส 2xx แจ้งผู้ส่งว่าเว็บฮุกได้รับการยอมรับแล้ว รหัส 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'})

การทดสอบเว็บฮุกในเครื่อง

ใช้ 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()

การบันทึกเหตุการณ์เว็บฮุก

บันทึกเว็บฮุกขาเข้าทุกรายการพร้อมเวลา แหล่งที่มา ประเภทเหตุการณ์ และผลการประมวลผล บันทึกตรวจสอบนี้จำเป็นอย่างยิ่งสำหรับการแก้ไขข้อบกพร่องของเหตุการณ์ที่ตกหล่นหรือปัญหาการประมวลผลซ้ำ

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'}
)

การจำกัดอัตราเว็บฮุกขาเข้า

ป้องกันไม่ให้ปลายทางเว็บฮุกรับภาระมากเกินไปด้วยการจำกัดอัตรา ไลบรารี 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')}

ตรวจสอบความรู้: เว็บฮุก

ทดสอบความเข้าใจเกี่ยวกับแนวทางปฏิบัติที่ดีที่สุดสำหรับเว็บฮุกของเอเจนต์

เว็บฮุกในระบบใช้งานจริง

ในระบบใช้งานจริง ให้ใช้รูปแบบทั้งหมดร่วมกัน ได้แก่ การตรวจสอบลายเซ็น คีย์ป้องกันการดำเนินการซ้ำ การประมวลผลเบื้องหลัง การบันทึกข้อมูลแบบมีโครงสร้าง และการจำกัดอัตรา นำระบบไปติดตั้งไว้หลังพร็อกซีย้อนกลับ เช่น nginx เพื่อยุติการเชื่อมต่อ TLS และเพิ่มการป้องกัน

คำถามที่พบบ่อย

บทเรียน “การเชื่อมต่อตัวแทนกับเว็บฮุก” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การเชื่อมต่อตัวแทนกับเว็บฮุก” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส AI Agents ให้อัปเกรดเป็น CoddyKit PRO คอร์ส AI Agents มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การเชื่อมต่อตัวแทนกับเว็บฮุก”

รับเหตุการณ์เว็บฮุกและเรียกใช้เวิร์กโฟลว์ตัวแทนเพื่อตอบสนอง คุณปฏิบัติ AI Agents ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน AI Agents หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน AI Agents บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “การเชื่อมต่อตัวแทนกับเว็บฮุก” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน AI Agents นี้ได้ไหม

ได้ บทเรียน AI Agents ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. รูปแบบตัวแทนทริกเกอร์-การดำเนินการ
  2. การเชื่อมต่อตัวแทนกับเว็บฮุก
  3. ตัวแทนตามกำหนดเวลาและ Cron
  4. การสร้างไปป์ไลน์อัตโนมัติสำหรับหลายแอป
← กลับไปที่ AI Agents