Webhooks

A webhook gives you a URL. Anything that posts to it starts a run of every deployment bound to it.

https://your-konnecthub-host/api/v1/webhooks/trigger/<token>

The token in that URL is the credential, so treat the URL as a secret. Any HTTP method is accepted — the body becomes the execution's payload, and the method, headers and query are recorded as the event's metadata.

What the caller gets back

StatusMeaning
201Recorded and enqueued — the normal fire-and-forget case
200The flow's own streamed response (waitForResponse webhooks only)
202Accepted but nothing ran: the webhook is off, no deployment is bound, or the target deployment is off and the delivery is held

The 202 body says which of those it was (inactive, noTargets, held), so a caller that cares can tell a misconfiguration from a discard.

Verifying the sender

The token proves the URL was known. To also prove the body was not tampered with, configure an HMAC:

SettingWhat it is
SecretThe shared key the sender signs with
LocationWhether the signature arrives in a header or a query parameter
KeyThe name of that header or parameter (X-Shopify-Hmac-Sha256, signature, …)
AlgorithmSHA256, SHA512 or MD5

The signature is computed over the raw body and compared in constant time. Providers disagree on how the digest is spelled — GitHub sends hex, Shopify sends base64 — so both encodings are accepted and you do not have to configure which.

Duplicate deliveries

Most providers retry, and most send a delivery id when they do. Name that header as the webhook's dedupe header (X-Shopify-Webhook-Id, X-GitHub-Delivery) and a repeat within the dedupe window is dropped instead of re-dispatched.

Without it, a provider's retry is a second, independent run.

Replying from the flow

By default the call returns as soon as the delivery is recorded — your flow has not started yet. For an endpoint whose caller needs a real answer, turn on wait for response: the caller is held open and the flow streams the reply.

async respond() {
  await this.sendResponseMeta({ status: 200, headers: { "content-type": "application/json" } });
  await this.sendResponse(JSON.stringify({ ok: true }), true); // `true` = final chunk
}

sendResponseMeta must come before the first sendResponse. Chunks are streamed as you write them, so a long answer starts arriving before the run finishes.

Feeding an event source instead of a deployment

A webhook can be pointed at an event source rather than at deployments. The delivery is converted into an event and fanned out to every subscription, and the caller gets 202.

This is what you want when several unrelated deployments should react to the same inbound delivery, and it is how platform services ingest their providers' webhooks.

Such a webhook has no bindings of its own — the subscriptions belong to the source.

The two switches

A delivery has to pass both:

SwitchOff means
The webhook's ownNothing is read, nothing is recorded, 202
The binding'sThis deployment is unwired; the webhook still fires and still records

Because an inactive webhook records nothing, its test delivery button is refused while it is off — a test that produced something a real delivery no longer would is a lie about the system.

A webhook with no bindings still records every delivery. That is the point: its statistics then read "3 deliveries, 0 runs", which is what tells you nobody was listening. It is not an error and does not stop the caller.

Expiry and limits

Next