Webhooks — overview
Pandoktorek POST'uje JSON do twojego URL gdy zachodzi event w klinice (np. nowa rezerwacja). Twój system odbiera, weryfikuje signature, fan-out'uje do swojego CRM (MyDr / Medfile / własnego).
Architektura
┌────────────────┐
│ Pandoktorek │
Pacjent rezerwuje ───────────► │ POST /bookings│
└────────┬───────┘
│
booking.created │
▼
┌──────────────────────────┐
│ WebhookDeliveryService │
│ - HMAC sign payload │
│ - httpx POST do twojego │
│ URL z headerami │
└──────────┬───────────────┘
│
▼ POST + sig
┌──────────────────────────┐
│ Twój system │
│ - verify HMAC │
│ - dedupe by event_id │
│ - fan-out do MyDr │
│ - return 200 │
└──────────────────────────┘
Setup webhooka
Panel klinki: Webhooks → Nowa subskrypcja.
URL: https://twój-system.pl/webhooks/pandoktorek
Events: ☑ booking.created
☑ booking.cancelled
☑ booking.completed
☐ booking.no_show
☐ review.created
Przy create dostajesz secret — zapisz go w .env. Nie pokażemy go ponownie.
Headers wysyłane przez Pandoktorek
Każdy POST do twojego URL:
POST /webhooks/pandoktorek HTTP/1.1
Host: twój-system.pl
Content-Type: application/json
X-Pandoktorek-Event: booking.created
X-Pandoktorek-Event-Id: 550e8400-e29b-41d4-a716-446655440000
X-Pandoktorek-Signature: sha256=4e8f3c1...
User-Agent: Pandoktorek-Webhook/1.0
{...payload...}
| Header | Opis |
|---|---|
X-Pandoktorek-Event | Typ eventu (np. booking.created) |
X-Pandoktorek-Event-Id | UUID eventu — używaj do dedup |
X-Pandoktorek-Signature | sha256=<hex> HMAC-SHA256 z body, klucz = secret subskrypcji |
Wymagania od twojego systemu
- Odpowiedź 2xx w ciągu 10s, inaczej Pandoktorek retry'uje
- Verify signature ZANIM przetworzysz event (anti-spoof)
- Dedupe by
event_id— Pandoktorek może retry'ować ten sam event - Idempotent processing — przetwarzanie 2× tego samego event'u nie może podwoić efektu w MyDr
Best practice — szkielet handlera
from fastapi import FastAPI, Request, HTTPException
import hmac, hashlib, redis
app = FastAPI()
SECRET = os.environ["PANDOKTOREK_WEBHOOK_SECRET"].encode()
r = redis.Redis()
@app.post("/webhooks/pandoktorek")
async def webhook(request: Request):
body = await request.body()
sig_header = request.headers.get("X-Pandoktorek-Signature", "")
expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig_header, expected):
raise HTTPException(401, "Invalid signature")
event = await request.json()
event_id = event["event_id"]
# Idempotent guard (24h dedup window)
if r.set(f"event:{event_id}", "1", ex=86400, nx=True) is None:
return {"status": "duplicate", "event_id": event_id}
# Fan-out do MyDr
if event["event_type"] == "booking.created":
mydr.create_appointment(event["payload"])
return {"status": "ok"}
→ Pełen przykład: Signature verification
Co dalej
- Signature → — HMAC verify w 4 językach
- Events → — pełna lista eventów + payloady
- Retry policy → — co się dzieje przy 5xx / timeout