Webhooks
Webhooks send each change the agents notice to an address of yours, as it happens, so your own tools can act on it: a message in your team chat when a product sells out, a row in a sheet when sales fall, a ticket when email sending is paused. It lives at Settings → Webhooks.
How do I add an address?
https address and choose Add address.A workspace can have up to five addresses. Each one gets every event.
What is sent?
Each event is a POST with a JSON body:
{
"id": "<event id>",
"type": "signal",
"createdAt": "2026-10-14T09:12:00.000Z",
"workspace": "my-store",
"data": {
"kind": "stock.out",
"agent": "maestro",
"subject": { "type": "shop_product", "id": "<product id>" },
"facts": { "productId": "<product id>", "title": "Ceramic mug", "units": 14 },
"text": { "en": "\"Ceramic mug\" sold out", "ro": "„Ceramic mug” s-a epuizat" },
"link": "https://kooga.io/o/my-store/store/products"
}
}idis the event's own id. The same event sent twice (a retry after your address answered too late) carries the sameid, so you can skip it.typeissignal, ortestfor the event the Send test button sends.data.kindsays what changed (see the table below), anddata.factsholds the numbers behind it. Which facts are there depends on the kind.data.textis the line you would read in Kooga, in English and Romanian.data.linkopens the place in Kooga to look.
| Kind | What changed |
|---|---|
rank.dropped | A keyword you track fell in Google. |
rank.top_three | A keyword you track reached Google's top three. |
article.declining | A published article is losing clicks. |
visibility.dropped | AI assistants mention your store less often. |
visibility.first_check | Your first AI visibility check finished. |
shelf.lost | A product dropped out of ChatGPT's shopping cards. |
email.paused | Email sending was paused for spam complaints. |
email.bounces | More emails than usual are bouncing. |
sales.dropped | Orders fell well below your usual week. |
stock.out | A product that sells sold out. |
stock.low | A product that sells is about to run out. |
stock.back | A product that had sold out is back in stock. |
New kinds may be added. Ignore the ones you do not use.
How do I check that an event came from Kooga?
Every event carries these headers:
| Header | Value |
|---|---|
Kooga-Event | signal or test |
Kooga-Delivery | The id of this delivery, the same on every retry |
Kooga-Signature | t=<unix seconds>,v1=<signature> |
The signature is the HMAC-SHA256, in hex, of the time, a dot, and the raw body exactly as received, made with your secret. To check it:
t and v1 from the Kooga-Signature header.t + "." + body with your secret, before parsing the body.v1 in constant time, and refuse the event if they differ.t is more than five minutes old, so an old one cannot be played back.In Node.js:
import crypto from 'node:crypto';
function fromKooga(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map(part => part.split('=')));
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && expected.length === parts.v1?.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}If a secret leaks, choose New secret beside the address. The old one stops working at once.
What happens when my address is down?
Answer with any 2xx status within ten seconds and the event counts as
delivered. Anything else, a redirect included, is tried again: after a
minute, five minutes, thirty minutes, two hours and twelve hours. After the
sixth try the event is given up. An address down for an afternoon still gets
everything.
While sends to an address fail, the card shows it as Failing, and the Latest events sent list under it says how your address answered each one. The next event that goes through clears it.
What's next
The same changes reach you in Kooga as cards on Home, and you choose what the agents do about each kind in Rules.