Odbieranie wyników

Webhooki z podpisem HMAC, ponowienia i ponowna wysyłka, odpytywanie jako alternatywa oraz odświeżanie wygasłych adresów pobierania.

Co osiągniesz

Generowanie jest asynchroniczne — wysyłasz sesję, a wyniki spływają po kilkudziesięciu sekundach lub minutach. W tym samouczku skonfigurujesz odbiór webhooków (z weryfikacją podpisu), poznasz odpytywanie jako prostszą alternatywę i nauczysz się odświeżać wygasłe adresy pobierania.

Wymagania wstępne

  • Klucz API instalacji (jak go zdobyć) z uprawnieniem plugin.jobs:read (do odpytywania) oraz plugin.webhooks:manage (do ponownej wysyłki webhooków).
  • Dla webhooków: publicznie dostępny adres HTTPS Twojej wtyczki, ustawiony jako callback_url instalacji, oraz sekret HMAC instalacji.

Przebieg

webhook (push)                  GET /jobs/{id} (pull)
  ├─ zweryfikuj podpis HMAC       ├─ sprawdzaj status
  ├─ odpowiedz 2xx                └─ pobierz outputs[].url
  └─ pobierz outputs[].url
wygasł adres? → POST /jobs/{id}/refresh-url
nieodebrany webhook? → POST /webhooks/{delivery_id}/replay

Kroki — webhooki

1. Odbierz powiadomienie

Gdy zadanie osiąga stan końcowy, wysyłamy POST na callback_url Twojej instalacji. Pole event przyjmuje wartości job.completed, job.failed lub job.cancelled.

{
  "event": "job.completed",
  "delivered_at": "2026-06-03T08:00:00.000Z",
  "job": {
    "id": "00000000-0000-0000-0000-0000000000a1",
    "status": "completed",
    "order_id": "00000000-0000-0000-0000-000000000099",
    "completed_at": "2026-06-03T07:59:40.000Z",
    "error": null
  },
  "outputs": [
    {
      "url": "https://…?token=…",
      "type": "image/jpeg",
      "width": 1024,
      "height": 1280
    }
  ],
  "external_metadata": { "sku": "SKU-001" }
}

Pole external_metadata wraca dokładnie takie, jakie wysłałeś przy tworzeniu sesji — użyj go, by dopasować wynik do swojego zamówienia (np. wpisz tam ID produktu ze swojego sklepu).

2. Zweryfikuj podpis

Każde powiadomienie ma nagłówek:

X-Qamera-Signature: t=<czas-unix>,v1=<hmac_sha256_hex>

Podpisywany jest ciąg <t>.<surowe-body> sekretem HMAC Twojej instalacji. Odrzucaj powiadomienia z t starszym niż 5 minut. Przykład w Node.js:

import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(rawBody, headerValue, secret) {
  const parts = Object.fromEntries(
    headerValue.split(',').map((p) => p.split('=')),
  );
  if (!parts.t || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;

  const expected = createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');
  return timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(parts.v1, 'hex'));
}

i w PHP:

function qamera_verify($rawBody, $header, $secret) {
  parse_str(strtr($header, ',', '&'), $parts);
  if (!isset($parts['t']) || !isset($parts['v1'])) return false;
  if (abs(time() - (int)$parts['t']) > 300) return false;
  $expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret);
  return hash_equals($expected, $parts['v1']);
}

Po wymianie sekretu (POST /installations/{id}/rotate-hmac) przez 48 godzin powiadomienia mają dwa segmenty v1= — jeden na stary, drugi na nowy sekret. Zaakceptuj dowolny z nich. Pełny kontrakt podpisu opisuje protokół webhook.

3. Odpowiedz szybko i przetwarzaj idempotentnie

  • Odpowiedz dowolnym statusem 2xx — najlepiej od razu, a przetwarzanie wykonaj w tle.
  • Przetwarzaj idempotentnie (np. po job.id + event): przy wolnej odpowiedzi możemy ponowić wysyłkę już dostarczonego powiadomienia.
  • Zapisuj (event, job.id, delivered_at) — przyda się przy diagnostyce.

4. Ponowienia i ponowna wysyłka

Jeśli Twój endpoint nie odpowiada 2xx, ponawiamy do 8 razy z rosnącym odstępem (do 1 godziny). Po 5 kolejnych nieudanych dostarczeniach wstrzymujemy wysyłkę na 30 minut; po 3 takich cyklach instalacja zostaje zawieszona.

Powiadomienie, którego nie udało się dostarczyć, możesz wysłać ponownie:

curl -X POST https://qamera.ai/api/v1/plugin/webhooks/00000000-0000-0000-0000-0000000000d1/replay \
  -H "X-Api-Key: mk_live_xxxxxxxx.yyyyyyyy"

Zwraca 202 z identyfikatorem nowego dostarczenia.

Kroki — odpytywanie (alternatywa)

Nie chcesz utrzymywać publicznego endpointu? Odpytuj o stan zadań:

# jedno zadanie
curl https://qamera.ai/api/v1/plugin/jobs/00000000-0000-0000-0000-0000000000a1 \
  -H "X-Api-Key: mk_live_xxxxxxxx.yyyyyyyy"

# wszystkie ukończone od wskazanej chwili
curl "https://qamera.ai/api/v1/plugin/jobs?status=completed&created_after=2026-06-03T00:00:00Z&limit=50" \
  -H "X-Api-Key: mk_live_xxxxxxxx.yyyyyyyy"

Wskazówki:

  • Odpytuj co 15–30 sekund, nie częściej — limit zapytań klucza (domyślnie 60/min) musi pomieścić też inne wywołania.
  • Stan całej sesji zbiorczo zwraca GET /orders/{id} — per produkt zobaczysz jobs_total, jobs_completed, jobs_failed i listę wyników.
  • Webhooki i odpytywanie można łączyć: webhook jako główny kanał, odpytywanie jako siatka bezpieczeństwa.

Wygasłe adresy pobierania

Adresy w outputs[].url są ważne co najmniej 7 dni. Po tym czasie pobierz świeże:

curl -X POST https://qamera.ai/api/v1/plugin/jobs/00000000-0000-0000-0000-0000000000a1/refresh-url \
  -H "X-Api-Key: mk_live_xxxxxxxx.yyyyyyyy"

Odpowiedź zawiera nowe outputs[] i expires_at. Najlepiej jednak pobierz pliki na swoją stronę zaraz po otrzymaniu wyniku — nie traktuj naszych adresów jako stałego hostingu.

Częste błędy

BłądDlaczego wystąpiłCo zrobić
Webhooki nie przychodząBrak callback_url na instalacji albo endpoint nie odpowiada 2xxUstaw callback_url w ustawieniach instalacji; sprawdź logi swojego endpointu
Weryfikacja podpisu nie przechodziWeryfikujesz przetworzone body zamiast surowego; zły sekret; minęło okno rotacjiPodpisuj dokładnie surowe bajty body; po rotacji zaktualizuj sekret w ciągu 48 h
409 przy replayOryginalne dostarczenie nie jest w stanie nadającym się do ponowieniaPonawiaj tylko nieudane/porzucone dostarczenia
409 job_not_completed przy refresh-urlZadanie jeszcze trwaPoczekaj na status: "completed"szczegóły
410 przy refresh-urlPliki zostały usunięte zgodnie z polityką przechowywaniaPobieraj pliki na swoją stronę od razu po wygenerowaniu — szczegóły

Co dalej