Zdarzenia i webhooki
WebSocket (aplikacja klientki)
Dział zatytułowany „WebSocket (aplikacja klientki)”const ws = new WebSocket(`wss://api.przymierz.online/v1/events?token=${token}`);ws.onmessage = (e) => { const event = JSON.parse(e.data); // {"type": "tryon.succeeded" | "tryon.failed" | "tryon.refused", "tryon_id": "...", "status": "...", "error_code": null} if (event.tryon_id === pendingId) showResult(); // GET /v1/tryons/{id} da result_url};Token przekazujesz w adresie, bo przeglądarki nie wysyłają nagłówka Authorization przy WebSocket. Gdy połączenie nie działa, odpytuj GET /v1/tryons/{id} co 2 sekundy.
Webhook
Dział zatytułowany „Webhook”Adres ustawiasz w panelu (Deweloper → Webhook). Wysyłamy POST z JSON.
type |
Kiedy |
|---|---|
tryon.succeeded |
Przymiarka gotowa |
tryon.failed |
Nie udała się, przyczyna w error_code (lista) |
tryon.refused |
Model odmówił wykonania |
tryon.rated |
Klientka oceniła wynik kciukiem |
ping |
Test z panelu (Wyślij test) |
Koniec przymiarki:
{ "type": "tryon.succeeded", "tryon_id": "6d572ac0-...", "status": "succeeded", "error_code": null, "external_user_id": "klientka-42"}Gdy klientka oceni wynik kciukiem, wysyłamy tryon.rated:
{ "type": "tryon.rated", "tryon_id": "6d572ac0-...", "rating": "down", "reasons": ["length"], "external_user_id": "klientka-42"}Powody: length, color, body_changed, not_me, other. Ponowna ocena tej samej przymiarki wysyła zdarzenie jeszcze raz.
Twój serwer powinien odpowiedzieć kodem 2xx w ciągu 10 sekund. Każdą próbę widać w panelu, a nieudaną możesz wysłać ponownie. Zdarzenie ping ma tylko pola type i sent_at.
Weryfikacja podpisu
Dział zatytułowany „Weryfikacja podpisu”Nagłówek X-Przymierz-Signature: t=<znacznik czasu>,v1=<podpis>. Podpis to HMAC-SHA256 z sekretem webhooka liczony z "{t}.{treść}". Odrzucaj zdarzenia starsze niż 5 minut.
import hashlib, hmac, time
def verify(secret: str, header: str, body: bytes) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) if abs(time.time() - int(parts["t"])) > 300: return False expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, parts["v1"])import crypto from "node:crypto";
export function verify(secret, header, rawBody) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false; const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.`).update(rawBody).digest("hex"); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));}Liczy się surowa treść zapytania (bajty), nie JSON po ponownym zapisie.