Webhooks and Integrations

Send events to your site with webhooks

Mis à jour 17 vues

A webhook is a URL of yours that Kookee calls when something happens in your project: a post is published, an article changes, a visitor leaves feedback. Use one to rebuild a static site when content changes, to refresh a cache, or to send events into your own systems.

Webhooks come with every plan. To post into Slack, Discord or Telegram, or to open issues in GitHub, Jira, Asana or Freshdesk, use Integrations instead: they format the message for each service.

Add an endpoint

  1. Open Webhooks under Connect in your project.

  2. Click Add endpoint and paste the URL.

  3. Pick the events it should receive, and click Add endpoint.

Add endpoint: a Netlify build hook URL with Entry published, updated, unpublished and deleted selected

Owners and admins manage webhooks. Members see a note instead of the endpoints, and guest accounts cannot add any.

Saving only checks the URL's shape. The address has to:

  • use https on the standard port;

  • name a host, not an IP address, with no user name or password in it;

  • not be localhost or end in .local, .internal or .home.arpa.

Each send then looks the host up again, and refuses it when any of its addresses is private, loopback, link-local or a cloud metadata address. A refused send is not retried.

The Webhooks page with two endpoints: a Vercel deploy hook for the four entry events, and the site's own receiver for new feedback and comments

Each endpoint shows its events, whether it is on, and its last attempt: when it was, and the status your server answered or what went wrong.

Events

Event

Sent when

entry.published

A blog post, page, help article, changelog entry or announcement becomes visible on your site: it is published, switched from chatbot only to public, or its scheduled date arrives.

entry.updated

A visible entry is saved and stays visible.

entry.unpublished

A visible entry stops being visible: it is unpublished, switched to chatbot only, moved to a future date, or its unpublish date passes.

entry.deleted

A visible entry is deleted.

feedback.created

A feedback post is created, including a post held for review.

comment.created

A comment is added to an entry or a feedback post.

incident.created

An incident on your status page is published, or a maintenance is scheduled. The incident can come from your team, an AI assistant or an uptime monitor.

incident.updated

An update is posted on a published incident or maintenance, including the ones a monitor posts when it goes down again or comes back up.

monitor.down

An uptime monitor is down: three checks in a row failed.

monitor.up

A monitor that was down is up again.

Scheduled dates are kept: a post set to go live at 9:00 sends entry.published at 9:00, and an announcement with an unpublish date sends entry.unpublished when it ends. Entries that were never visible send nothing, so a draft saved ten times is quiet. Feedback posts send feedback.created, never entry.*.

The dashboard lists the status events as Incident published or maintenance scheduled, Incident updated, Monitor down and Monitor up again. A draft incident sends nothing until it is published. Nothing is sent when an incident or a maintenance is edited or deleted, when a component's status is set by hand, or when a maintenance window starts or ends on schedule. A monitor that has never been up sends no monitor.down: it only alerts your owners and admins. During a maintenance of its component, a monitor holds its monitor.down until the window ends.

Send test sends a webhook.test event at once and shows what your server answered. It works while an endpoint is turned off, and it never counts toward turning one off.

What your server receives

Every event is a POST with a JSON body:

JSON
{
  "type": "entry.published",
  "timestamp": "2026-10-09T09:00:00.000Z",
  "data": {
    "project": { "id": "4b1f…", "slug": "lumen", "name": "Lumen" },
    "id": "9a3c…",
    "type": "blog",
    "locale": "en",
    "slug": "dashboards-that-explain-themselves",
    "title": "Dashboards that explain themselves",
    "translationGroupId": "77e0…",
    "publishedAt": "2026-10-09T09:00:00.000Z"
  }
}

feedback.created carries the post's id, title, its text as contentText (up to 1,000 characters), status (draft while it waits for review), category and author (id, name, isTeamMember). comment.created carries the comment's contentText, isOfficial, author (id, isTeamMember) and target, the entry it was left on. No email address is ever sent.

incident.created and incident.updated carry the incident's id, kind (incident or maintenance), title, status, impact, its components with the status each shows now, scheduledStartAt and scheduledEndAt for a maintenance, resolvedAt, the update that caused the event (id, status and body, in Markdown), and pageUrl, the incident's page on your status page, or null while that page is not public. monitor.down and monitor.up carry the monitor's id, name, url and component, and its outage: id, startedAt, endedAt and durationSeconds once it is over, cause and statusCode.

The request carries these headers:

Header

Value

webhook-id

msg_ and the event's id, the same on every retry

webhook-timestamp

When this attempt was signed, in Unix seconds

webhook-signature

v1, and the signature

user-agent

Kookee-Webhooks/1.0

Verify the signature

Kookee signs every request the Standard Webhooks way, so any of its libraries checks it. Find the endpoint's signing secret in its menu: Reveal signing secret shows it, and Regenerate signing secret replaces it, after which the old one stops working at once.

TypeScript
import { Webhook } from 'standardwebhooks';

const webhook = new Webhook(process.env.KOOKEE_WEBHOOK_SECRET!); // whsec_…

export async function POST(request: Request) {
  const body = await request.text();
  let event;
  try {
    event = webhook.verify(body, Object.fromEntries(request.headers));
  } catch {
    return new Response('Invalid signature', { status: 400 });
  }

  if (event.type === 'entry.published') {
    // rebuild, revalidate, notify…
  }
  return new Response(null, { status: 204 });
}

Verify the raw body, before parsing it: re-serialised JSON no longer matches the signature. The library also refuses requests whose timestamp is more than five minutes off, so a captured request cannot be replayed later.

Rebuild a static site on publish

A deploy hook from Vercel, Netlify or Cloudflare Pages is a URL that starts a build when it receives a POST. Add it as an endpoint with the four entry.* events, and your site rebuilds whenever content goes live, changes, comes down or is deleted, including at a scheduled date. Build hooks do not check signatures, so keep the hook's URL to yourself.

Retries, and when an endpoint turns off

Answer with any 2xx status within 10 seconds. Kookee does not follow redirects, and reads only the start of your answer.

  • A failed delivery is retried up to 10 times over about 8 and a half hours, waiting 1, 2, 4 and up to 256 minutes between tries. A retry keeps its webhook-id, so de-duplicate on it, and order events by timestamp rather than by arrival.

  • 410 Gone turns the endpoint off at once. Answer it when the URL is retired for good.

  • Five events in a row that fail every retry turn it off too.

When an endpoint turns off, the project owner gets a notification and an email. Fix your server, then click Turn back on on the endpoint.

Limits

Free

Pro

Ultra

Endpoints per project

2

10

50

Events an hour, across the account

100

1,000

1,000

Events an hour, per endpoint

300

300

300

The hourly limits count content events (entry.*) and visitor events (feedback.created, comment.created) apart, so a burst of feedback cannot use up what publishing needs. Status events (incident.*, monitor.*) are counted apart as well, so a busy hour of publishing cannot hold back an outage alert. Events over them are dropped, and the endpoint shows Events dropped: over the hourly limit.

A slow server is limited by time as well: each endpoint can spend 2 and a half minutes an hour waiting for answers, and an account 7 and a half minutes across its endpoints. Past that, events are skipped until the hour turns and the endpoint shows Skipped: the receiver is too slow this hour. A skip never counts toward turning an endpoint off. Answer quickly and do the slow work after responding.

Adding an endpoint past the plan's limit is refused with WEBHOOK_LIMIT_REACHED. Rate limits, plans and error handling lists every limit.

Continue reading

Cet article vous a-t-il aidé ?