Webhooki
Webhook to sposób, w jaki OctaForms powiadamia Twój serwer o nowym zgłoszeniu. Po wysłaniu
formularza OctaForms robi POST na skonfigurowany adres URL z danymi leada w formacie JSON.
Endpoint i sekret podpisu ustawiasz w panelu, dla każdego formularza osobno.
Żądanie, które dostajesz
Dział zatytułowany „Żądanie, które dostajesz”POST /twoj-endpoint HTTP/1.1Content-Type: application/jsonX-OctaForms-Signature: t=1752573000, v1=3f9a...c1
{ "id": "018f5c2e-...", "formId": "7c2e1a9d-...", "overviewUrl": "https://panel.octaforms.pl/leads/018f5c2e-...", "created": "2026-07-15T10:30:00+0000", "fields": { "name": "Jan Kowalski", "email": "jan@example.com", "phone": "+48500600700", "zgoda": true }, "meta": { "form-url": "https://twoja-strona.pl/kontakt", "referrer": "https://www.google.com/" }}Pola payloadu
Dział zatytułowany „Pola payloadu”| Pole | Typ | Znaczenie |
|---|---|---|
id |
string | Identyfikator zgłoszenia (leada). Użyj go do deduplikacji po swojej stronie. |
formId |
string | Identyfikator formularza, z którego przyszło zgłoszenie. |
overviewUrl |
string (URL) | Link do podglądu zgłoszenia w panelu OctaForms. |
created |
string | Czas utworzenia zgłoszenia w formacie ISO 8601, Y-m-d\TH:i:sO (np. 2026-07-15T10:30:00+0000, offset UTC bez dwukropka). |
fields |
obiekt | Wartości pól formularza, kluczowane nazwą pola. Pola typu checkbox są typu boolean. |
meta |
obiekt | Kontekst dołączony przez widget na stronie. Przekazywany bez zmian. Traktuj klucze jako opcjonalne. |
Klucze meta
Dział zatytułowany „Klucze meta”meta to otwarty worek, klucze bywają obecne zależnie od konfiguracji na stronie. Najczęstsze:
| Klucz | Typ | Znaczenie |
|---|---|---|
form-url |
string (URL) | Pełny adres strony, na której wysłano formularz. |
referrer |
string | Adres, z którego trafił użytkownik (surowy document.referrer, pusty gdy brak). |
context |
string | Tylko widget kontaktowy („słuchawka“): wartość contact-widget. |
tab |
string | Tylko widget kontaktowy: callback lub message. |
Weryfikacja podpisu
Dział zatytułowany „Weryfikacja podpisu”Każde żądanie ma nagłówek:
X-OctaForms-Signature: t=<unix-timestamp>, v1=<hmac_sha256(sekret, t + "." + rawBody)>tto znacznik czasu (sekundy Unix) z momentu wysłania.v1to HMAC SHA-256 liczony z sekretu i wiadomościt + "." + rawBody, w zapisie hex (małe litery).rawBodyto surowe bajty ciała żądania. Licz HMAC z nieprzetworzonego body, nie z ponownie zserializowanego JSON-a, inaczej podpis się nie zgodzi.
Podczas rotacji sekretu nagłówek może nieść dwa człony v1= (nowy i poprzedni sekret). Zaakceptuj
żądanie, jeśli którykolwiek się zgadza.
- Odczytaj surowe body żądania.
- Wyciągnij
tz nagłówka (wzorzect=(\d+)). - Odrzuć żądanie, jeśli
abs(now - t) > 300sekund (ochrona przed replay, tolerancja ±5 min). - Policz
expected = HMAC_SHA256(sekret, t + "." + rawBody)jako hex. - Wyciągnij wszystkie wartości
v1=([a-f0-9]+)z nagłówka. - Porównaj
expectedz każdymv1w sposób odporny na timing (hash_equals/timingSafeEqual). Zaakceptuj, jeśli którykolwiek pasuje. - Przy niezgodności odpowiedz
400.
Przykład (Node.js / Express)
Dział zatytułowany „Przykład (Node.js / Express)”import crypto from 'node:crypto';
const SECRET = process.env.OCTAFORMS_WEBHOOK_SECRET;
// ważne: potrzebujemy surowego body, więc bez globalnego express.json()app.post('/webhooks/octaforms', express.raw({ type: 'application/json' }), (req, res) => { const header = req.get('X-OctaForms-Signature') || ''; const rawBody = req.body; // Buffer z surowymi bajtami
const t = (header.match(/t=(\d+)/) || [])[1]; if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) { return res.status(400).end(); }
const expected = crypto .createHmac('sha256', SECRET) .update(`${t}.${rawBody.toString('utf8')}`) .digest('hex');
const signatures = [...header.matchAll(/v1=([a-f0-9]+)/g)].map((m) => m[1]); const ok = signatures.some((sig) => { const a = Buffer.from(expected, 'hex'); const b = Buffer.from(sig, 'hex'); return a.length === b.length && crypto.timingSafeEqual(a, b); });
if (!ok) return res.status(400).end();
const lead = JSON.parse(rawBody.toString('utf8')); // ...obsłuż zgłoszenie... res.status(200).end();});Sekret znajdziesz i zrotujesz w panelu OctaForms, przy konfiguracji webhooka. Rotacja nie unieważnia
od razu starego sekretu, przez chwilę oba są ważne (stąd dwa człony v1=).
Ponawianie i limity
Dział zatytułowany „Ponawianie i limity”- Timeouty: połączenie 5 s, całe żądanie 10 s. Endpoint musi odpowiedzieć w tym czasie.
- Sukces: dowolna odpowiedź
2xx. Każdy inny status (4xx/5xx) albo timeout to porażka. - Ponawianie: do 3 prób po pierwszej dostawie, z narastającym odstępem 1 s, 2 s, 4 s.
- Deduplikacja: dostawa jest „co najmniej raz“. Przy ponowieniu OctaForms wysyła ten sam payload
z tym samym
id. Deduplikuj po poluid.