Webhookok
A Posty szól a rendszerednek, ha egy bejegyzés kiment vagy elbukott. Események, a törzs alakja és az aláírás ellenőrzése.
K čemu slouží
Webhook funguje obráceně: Posty se nedotazujete, ozveme se my vašemu systému, když se něco stane. Vyšel příspěvek, selhalo publikování, odpojil se kanál.
Funkce je dostupná od tarifu Pro. Najdete ji v levém menu v části Automatizace, na kartě Webhooky. Tarif určuje, kolik webhooků můžete mít.
Vytvoření webhooku
- 1Přidat webhookZadejte mu název a adresu koncového bodu, který bude požadavky přijímat. Musí to být adresa HTTPS.
- 2Vyberte událostiMůžete vybrat několik, nebo seznam nechat prázdný. Prázdný seznam znamená všechno, včetně událostí přidaných později.
- 3Vyberte kanályWebhook můžete omezit na konkrétní kanály, pokud vás zajímají jen ty.
- 4Uložte ho a zapište si tajný klíč k podpisuTajný klíč začíná předponou
whsec_. V editoru webhooku ho můžete kdykoli zobrazit a znovu vygenerovat.
Když nevyberete žádnou událost, dostanete všechny, včetně těch, které přidáme později. Když vyberete šest, dostanete přesně těch šest. Sedmá, později přidaná událost na tento webhook nedorazí.
Události
| Událost | Kdy |
|---|---|
post.published | Příspěvek vyšel na kanál. |
post.failed | Příspěvek skončil ve stavu chyby: platforma ho odmítla, token přestal platit, kanál byl offline nebo vypnutý, nebo to limit tarifu nedovolil. |
channel.disconnected | Přístup skončil a kanál přešel do režimu offline. Fronta tohoto kanálu nic nepublikuje, dokud ho znovu nepřipojíte. |
channel.refresh_needed | Obnovení tokenu se zaseklo v polovině. Kanál není offline, ale připojení musíte dokončit ručně. |
channel.disabled | Kanál je vypnutý: vypnuli jste ho vy, nebo ho po změně tarifu vypnulo odsouhlasení fakturace. |
channel.enabled | Pozastavený kanál se znovu zapnul. |
Událost channel.connected neexistuje: připojení probíhá v několika krocích a mezistavy nemá smysl hlásit.
Každé doručení nese i hlavičku X-Posty-Event. Řiďte se ale polem event v těle: hlavička v podpisu není, pole ano.
Tělo požadavku
Tělo je vždy JSON pole, u každé události. Nepředpokládejte, že má vždy délku jedna.
post.published
[
{
"id": "cm6tcts4f0005qcwit25cis26",
"content": "Ez az első bejegyzés Instagramra",
"publishDate": "2026-09-15T09:00:00.000Z",
"releaseURL": "https://instagram.com/p/...",
"state": "PUBLISHED",
"integration": {
"id": "cm6s4uyou0001i2r47pxix6z1",
"name": "Posty",
"providerIdentifier": "instagram",
"picture": "https://uploads.posty.hu/...jpeg",
"type": "social"
},
"event": "post.published"
}
]publishDate je UTC, jako každé časové razítko v Posty.
post.failed
Stejný objekt, navíc s polem error. Hodnota state je ERROR a releaseURL je null, protože k publikování nedošlo. error je vlastní věta platformy, pokud nějaká je, jinak null.
Události channel
[
{
"event": "channel.disconnected",
"reason": "token_revoked",
"detail": "— access was removed in the account's Meta settings",
"integration": {
"id": "cm6s4uyou0001i2r47pxix6z1",
"name": "Posty",
"providerIdentifier": "instagram",
"picture": "https://uploads.posty.hu/...jpeg",
"type": "social"
}
}
]reason je uzavřená sada: token_revoked, refresh_failed, manual, plan_limit. Podle ní větvete. detail je volný text anglicky, nebo null: čte ho člověk, ne váš kód.
Ověření podpisu
Každé doručení je podepsané, v hlavičce X-Posty-Signature:
X-Posty-Signature: t=1770000000,v1=10bd17b41e83eee170010df01daeeaa0edf2f939afa6e97f41e1fa7bd27e6191t: unixový čas v sekundách, UTC, v okamžiku podpisu.v1: hexadecimální hodnota malými písmenyHMAC-SHA256(tajemství, "<t>.<surové tělo>").- Klíčem je tajný klíč k podpisu daného webhooku, s předponou
whsec_.
Je to záměrně stejný tvar jako u podpisu Stripe, takže existující ověřovací knihovny Stripe s ním fungují. Stačí změnit název hlavičky a tajný klíč.
JSON.stringify není kanonický: pořadí klíčů, escapování unicode a formátování čísel se liší podle implementace. Přijímající systém, který tělo nejdřív zpracuje a pak ho znovu převede na řetězec, spočítá jiný hash a odmítne naprosto v pořádku doručení. Tělo načtěte surově, před zpracováním.
Ověření krok za krokem
- Načtěte surové tělo a hlavičku X-Posty-Signature.
- Hlavičku rozdělte podle čárek a každou část podle prvního rovnítka. Tak získáte hodnoty t a v1.
- Odmítněte ho, pokud je rozdíl mezi t a aktuálním časem větší než tolerance. Počítáme s tolerancí 300 sekund. To je ochrana proti přehrání.
- Spočítejte HMAC-SHA256(tajemství, t + "." + surové tělo) jako hexadecimální hodnotu malými písmeny.
- Porovnejte výsledek s hodnotou v1 porovnáním v konstantním čase.
const crypto = require('crypto');
function verify(secret, rawBody, header, toleranceSeconds = 300) {
const parts = Object.fromEntries(
header.split(',').map((p) => {
const at = p.indexOf('=');
return [p.slice(0, at).trim(), p.slice(at + 1).trim()];
})
);
const t = Number(parts.t);
if (!Number.isFinite(t)) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSeconds) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
return (
expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
);
}Pro ruční ověření zachyceného doručení totéž ze shellu:
printf '%s' "$T.$RAW_BODY" | openssl dgst -sha256 -hmac "$SECRET" -hexv1 je verzovaný záměrně: kdyby se schéma někdy změnilo, doručení ponesou po přechodnou dobu starý i nový prvek. Berte prvek, jehož názvu rozumíte, a zbytek ignorujte. Nepředpokládejte, že hlavička má přesně dvě části.
Co musí přijímající systém vědět
- Než cokoli čtete, řiďte se polem
eventv těle a neznámé události ignorujte: přibudou další. - Jako klíč použijte pole
idpříspěvku, nebo dvojiciintegration.ida událost, a opakované doručení zpracujte idempotentně. - Čekejte jedno doručení na kanál, ne jedno na skupinu příspěvků, a nepředpokládejte pořadí.
- Při špatném podpisu požadavek odmítněte a zapište ho do logu. Jen tak bude podvržené POST vidět.
Pokud se na události chcete dotazovat, ne je přijímat, koncový bod Oznámení vrací tytéž události jako seznam.