Webhooky
Otto pošle na vašu adresu POST, keď sa niečo stane: nová konverzácia, odovzdanie, kontakt, reklamácia, načítaný katalóg. Podpis podľa Standard Webhooks.
Nastavenie
- V paneli otvorte Nastavenia, časť Prepojenia, riadok Webhooky.
- Zadajte verejnú adresu
https://vášho servera a vyberte udalosti. Keď necháte označené všetky, pošleme aj udalosti, ktoré pribudnú neskôr. - Uložte. Panel ukáže podpisový kľúč
whsec_…, uložte si ho k overeniu podpisu. - Tlačidlom Poslať skúšobnú udalosť overíte spojenie hneď. Príde udalosť
test.ping.

Udalosti
| Udalosť | Kedy príde | data |
|---|---|---|
conversation.started | Začala nová konverzácia Keď zákazník napíše prvú správu. | { conversationId, question } |
conversation.handoff | Zákazník chce človeka Otto nevie ďalej alebo zákazník vyslovene žiada človeka. | { conversationId, question, confidence } |
conversation.operator_joined | Rozhovor prevzal človek Niekto z tímu prevzal rozhovor v Schránke. | { conversationId, operator } |
conversation.resolved | Konverzácia je vyriešená Označili ste ju v Schránke ako vyriešenú. | { conversationId } |
conversation.rated | Zákazník ohodnotil konverzáciu Palec hore alebo dole na konci konverzácie. | { conversationId, rating } |
message.created len na výber | Nová správa v konverzácii Každá správa zákazníka, asistenta aj kolegu. Je ich veľa, zapnite ju, len keď ich naozaj spracujete. | { id, conversationId, role, kind, text, author } |
lead.created | Zákazník zanechal kontakt Meno, e-mail alebo telefón zadaný priamo v chate. | { id, conversationId, name, email, phone } |
lead.updated | Kontakt je vybavený alebo znova otvorený Niekto v paneli alebo cez API zmenil, či je kontakt vybavený. | { id, conversationId, handled } |
return.created | Nová reklamácia alebo vrátenie Zákazník odoslal formulár aj s prílohami. | { id, kind, orderId } |
return.updated | Zmenil sa stav podnetu Schválili ste ho, zamietli alebo vybavili. | { id, kind, orderId, status } |
unanswered.created | Neistá odpoveď Otázka, na ktorú sa vo vašich znalostiach nenašla istá odpoveď. | { conversationId, question } |
catalog.synced | Katalóg sa načítal Feed produktov sa načítal, príde aj počet produktov. | { source, products } |
catalog.failed | Katalóg sa nenačítal Feed produktov sa nepodarilo načítať. Príde pri prvom zlyhaní, nie pri každom ďalšom. | { source, error } |
usage.threshold | Míňa sa limit AI odpovedí Príde pri 80 % a pri 100 % limitu AI odpovedí v pláne. | { used, limit, level } |
Udalosť message.created chodí len vtedy, keď ju zapnete sami. Je jej veľa a tlačidlo Označiť všetky ju vynechá.
Telo požiadavky
{
"id": "1b6f6a3e-0f7c-4a51-9a3d-6a2f0e8c4b11",
"event": "lead.created",
"at": "2026-10-10T08:14:03.512Z",
"apiVersion": "1",
"tenantId": "3f6c2a10-8d4e-4c1b-a6f2-9e0d7b5c3a21",
"tenant": "Váš obchod",
"data": {
"id": "8353028d-e582-4e08-8ed9-f882d200cd22",
"conversationId": "37e7d86d-57bf-455f-9fc6-8c856a607d2f",
"name": "Ján Novák",
"email": "jan@example.sk",
"phone": ""
}
}id je pri opakovaní rovnaké. Keď to isté id dorazí znova, záznam zahoďte.
Overenie podpisu
Podpisujeme podľa štandardu Standard Webhooks, overenie zvládne aj hotová knižnica pre Node.js, PHP, Python a ďalšie jazyky. Každá požiadavka nesie tri hlavičky:
webhook-id | id udalosti, pri opakovaní rovnaké |
webhook-timestamp | čas pokusu v sekundách od 1. 1. 1970 |
webhook-signature | v1,<podpis>, kde podpis je Base64 z HMAC-SHA256 nad id.timestamp.telo |
Kľúčom HMAC je podpisový kľúč bez predpony whsec_, dekódovaný z Base64. Podpis počítajte zo surového tela, ešte pred parsovaním JSON. Pokus odmietnite, keď podpis nesedí alebo je čas starší ako 5 minút.
import crypto from "node:crypto";
export function overPodpis(secret, hlavicky, suroveTelo) {
const id = hlavicky["webhook-id"];
const cas = hlavicky["webhook-timestamp"];
const kluc = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const ocakavany = crypto.createHmac("sha256", kluc).update(id + "." + cas + "." + suroveTelo).digest("base64");
const sedi = String(hlavicky["webhook-signature"] || "").split(" ").some((cast) => {
const [verzia, podpis] = cast.split(",");
return verzia === "v1" && podpis && podpis.length === ocakavany.length
&& crypto.timingSafeEqual(Buffer.from(podpis), Buffer.from(ocakavany));
});
return sedi && Math.abs(Date.now() / 1000 - Number(cas)) < 300;
}function overPodpis(string $secret, array $h, string $suroveTelo): bool {
$kluc = base64_decode(preg_replace('/^whsec_/', '', $secret));
$sprava = $h['webhook-id'] . '.' . $h['webhook-timestamp'] . '.' . $suroveTelo;
$ocakavany = base64_encode(hash_hmac('sha256', $sprava, $kluc, true));
foreach (explode(' ', $h['webhook-signature'] ?? '') as $cast) {
[$verzia, $podpis] = array_pad(explode(',', $cast, 2), 2, '');
if ($verzia === 'v1' && hash_equals($ocakavany, $podpis)) {
return abs(time() - (int) $h['webhook-timestamp']) < 300;
}
}
return false;
}import base64, hashlib, hmac, time
def over_podpis(secret: str, h: dict, surove_telo: bytes) -> bool:
kluc = base64.b64decode(secret.removeprefix("whsec_"))
sprava = f"{h['webhook-id']}.{h['webhook-timestamp']}.".encode() + surove_telo
ocakavany = base64.b64encode(hmac.new(kluc, sprava, hashlib.sha256).digest()).decode()
sedi = any(
hmac.compare_digest(cast.split(",", 1)[1], ocakavany)
for cast in h.get("webhook-signature", "").split()
if cast.startswith("v1,")
)
return sedi and abs(time.time() - int(h["webhook-timestamp"])) < 300Pôvodnú hlavičku X-Otto-Signature: sha256=<HMAC_SHA256(secret, telo) v hex> posielame naďalej, staršie integrácie fungujú bez zmeny.
Opakovanie a vypnutie
Odpovedzte kódom 2xx do 5 sekúnd, prácu robte až potom. Keď odpoveď nepríde alebo nie je 2xx, skúsime to znova v týchto odstupoch: hneď, 5 s, 5 min, 30 min, 2 h, 5 h, 10 h, 10 h, 14 h, 24 h, spolu asi 3 dni. Udalosti čakajú v databáze, neprepadnú ani pri našom reštarte. Pri kóde 4xx okrem 408 a 429 neopakujeme.
Keď 3 dni nedoručíme ani jednu udalosť, webhook vypneme a pošleme vám e-mail. Zapnete ho tým, že ho v paneli znova uložíte.
Doručenia cez API
GET /api/v1/webhooks/deliveries?status=failedvypíše doručenia s počtom pokusov, kódom odpovede a chybou.POST /api/v1/webhooks/deliveries/<id>/resendpošle udalosť znova. Obsah udalosti držíme 7 dní po poslednom pokuse.POST /api/v1/webhooks/testpošle skúšobnú udalosť a hneď povie, ako vaša adresa odpovedala.

