Skip to content

Register a webhook trigger

View as Markdown
POST
/v1/webhooks
curl --request POST \
--url https://example.com/v1/webhooks \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "flowId": "example", "flowVersion": "example", "projectId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "config": { "input": "example" }, "hmacSecretName": "example", "generateSecret": true, "signature": { "kind": "hmac-sha256", "encoding": "hex", "header": "example", "prefix": "example" }, "deliveryIdHeader": "example", "bodyLimitBytes": 1, "rateLimitPerMinute": 1, "label": "example", "runAsServiceAccountId": "example" }'

A signed request to the trigger’s receiveUrl (POST /v1/hooks/{tenantId}/{webhookId}) starts a run of its flow, as its owner: the caller, until an admin takes it over. The route mints webhookId (a random UUID). The signing secret is written first (POST /v1/secrets), and hmacSecretName names it; a model provider’s key is refused (400 provider-key-refused). Where it’s read is recorded on the trigger (hmacSecretScope): the first of its project, the project’s org and the tenant that has it and that the caller administers, else the broadest they administer; the receiver reads it there only. Or generateSecret: true: the server makes it, stores it as hmacSecretName in the trigger’s own project (a name the project already has is refused, 409 secret-write-conflict), and returns it once as signingSecret. signature says how the sender signs (default: a hex HMAC-SHA256 of the raw body in X-Kindgi-Signature); deliveryIdHeader names the header whose value, with the body, dedupes deliveries. A trigger that names no projectId goes in the tenant’s Default project. Needs write on the project and execute on the flow.

Idempotency-Key
string
>= 1 characters

Caller-supplied idempotency key, scoped to the caller: the same key from someone else in the tenant is their own request (from 0.1.5). Retries with the same key return the original response byte-identical (per docs/API-ROUTE-CONVENTIONS.md §3.1). A retry sent while the first request still runs is answered 409 idempotency-key-in-flight with Retry-After, on a runtime whose store holds keys (from 0.1.5); retry after it, and you get the first request’s answer. An answer that carries a secret (a new API key or public run token, a session token, a generated signing secret) isn’t kept: a retry gets 409 idempotency-key-replay-withheld, with the first request’s status and when it succeeded (from 0.1.5).

Media typeapplication/json
object
flowId
required
string
>= 1 characters
flowVersion
required
string
>= 1 characters
projectId

The trigger’s project; absent: the tenant’s Default project.

string format: uuid
config
object
input
hmacSecretName
required

The signing secret, by name: written first with POST /v1/secrets, and read at the first of the trigger’s project, its org and the tenant that has it and that you administer (hmacSecretScope); or the name generateSecret stores a new one under. Never a model provider’s key. For WooCommerce, use letters and digits only (it HTML-decodes the secret before signing). Under kindgi dev, generateSecret writes it to an env file, so the name is capital letters, digits and underscores, not starting with a digit (secret-name-invalid otherwise).

string
>= 1 characters
generateSecret

Make a new signing secret, store it as hmacSecretName in the trigger’s own project (in the env the deployment serves), and return it once as signingSecret. Needs only write on the project. A name the project already has is refused (secret-write-conflict, 409), never overwritten. Letters and digits, or whsec_… for standard-webhooks.

boolean
signature
One of:
object
kind
required
string
Allowed values: hmac-sha256
encoding
required
string
Allowed values: hex base64
header
required

The header that carries the signature (matched case-insensitively).

string
>= 1 characters <= 100 characters
prefix

Stripped from the header’s value before decoding (e.g. sha256=).

string
>= 1 characters <= 32 characters
deliveryIdHeader

The header whose value, with the body, dedupes deliveries. Not with standard-webhooks, which dedupes on its webhook-id.

string
>= 1 characters <= 100 characters
bodyLimitBytes
integer
>= 1024 <= 1048576
rateLimitPerMinute
integer
>= 1 <= 6000
label
string
runAsServiceAccountId

A service account the trigger’s runs act as (its owner), named by id, instead of the caller. Needs act_as on that account — a tenant admin holds it — and the account must itself hold execute on the flow. Absent: the caller owns it.

string
>= 1 characters

Webhook registered.

Media typeapplication/json

A webhook trigger: a signed request to its receiveUrl starts a run of its flow, as its owner. The signing secret is never on it, only its name.

object
triggerId
required
string
webhookId
required

The routable id in the receive URL. Route-minted (a random UUID); unique per tenant. Not a secret: the signature is what a request is checked by.

string
receiveUrl

Where the sender posts: {public URL}/v1/hooks/{tenantId}/{webhookId}. Absent when the deployment has no public URL configured (KINDGI_PUBLIC_URL); it’s never taken from a request’s Host.

string format: uri
flowId
required
string
>= 1 characters
flowVersion
required
string
>= 1 characters
projectId
required

The trigger’s project: its runs are this project’s.

string format: uuid
owner
required

Who its runs act as: whoever registered it, until an admin takes it over (POST …/owner). Checked again at every delivery.

object
kind
required
string
Allowed values: user service
id
required
string
displayName

The owner’s name at the time of the response: the person’s display name, or the service account’s name. Absent when it can’t be read (no directory, a removed account) and from a runtime before Kindgi 0.1.6: show the id then.

string
input

The flow’s input on every delivery, in place of the event. Absent: the event (the body, parsed as JSON for a JSON content type, else its text).

hmacSecretName
required

The signing secret, by name (written with POST /v1/secrets, or made by generateSecret). Read in the env the deployment serves, at hmacSecretScope.

string
>= 1 characters
hmacSecretScope
One of:
One of:
object
kind
required
string
Allowed value: tenant
signingSecret

Only in the answer to a register with generateSecret: the new signing secret, shown this once, to paste into the sender. It’s stored as hmacSecretName in the trigger’s own scope; no later read shows it.

string
signature
required
One of:
object
kind
required
string
Allowed values: hmac-sha256
encoding
required
string
Allowed values: hex base64
header
required

The header that carries the signature (matched case-insensitively).

string
>= 1 characters <= 100 characters
prefix

Stripped from the header’s value before decoding (e.g. sha256=).

string
>= 1 characters <= 32 characters
deliveryIdHeader

The header whose value, with the body, is a delivery’s dedupe key (WooCommerce: X-WC-Webhook-Delivery-ID, which is per second, not per event, so the body counts too). Absent: deliveries aren’t deduped (standard-webhooks dedupes on its own webhook-id).

string
bodyLimitBytes
required

The largest body it takes (default 262144).

integer
>= 1024 <= 1048576
rateLimitPerMinute
required

Accepted deliveries a minute (default 600); past it, 429.

integer
>= 1 <= 6000
label
required
string | null
status
required

Lifecycle status. Only active triggers fire. Tombstoned rows are excluded from every read path.

string
Allowed values: active paused
statusReason

Why the runtime paused it (repeated refused or failed starts; a sender’s failed proofs never count), when it did.

string
suppressedRefusals

Refusals and skipped deliveries this minute past the 20 recorded in its history, which only counted.

object
since
required
string format: date-time
count
required
integer
>= 1
lastFiredAt
required
string | null format: date-time
createdAt
required
string format: date-time
updatedAt
required
string format: date-time
Example
{
"owner": {
"kind": "user"
},
"hmacSecretScope": {
"kind": "tenant"
},
"signature": {
"kind": "hmac-sha256",
"encoding": "hex"
},
"status": "active"
}

A body that fails validation (bad-input), or provider-key-refused: hmacSecretName is a model provider’s key. Or reference-out-of-scope: the flow isn’t registered in the trigger’s project, its org or the tenant, or flowVersion isn’t a version that project runs (details.issues, at /flowId, whose message names the version), and nothing is registered. Or, with generateSecret, a name the secrets store can’t write, refused before anything is stored: secret-name-invalid (a name that isn’t 1 to 255 ASCII letters, digits, ., - and _ starting with a letter, digit or _, with any . between two non-. characters, or, under kindgi dev, isn’t capital letters, digits and underscores, starts with a digit, or is KINDGI_*; the message says the rule) or reserved-name-prefix.

Media typeapplication/json
object
error
required
object
code
required

Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).

string
message
required
string
details

Optional, kind-specific.

object
key
additional properties
any
requestId
required

Server-assigned request id; also echoed via X-Request-Id header.

string
Examplegenerated
{
"error": {
"code": "example",
"message": "example",
"details": {},
"requestId": "example"
}
}

Missing / malformed / expired / revoked bearer token.

Media typeapplication/json
object
error
required
object
code
required

Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).

string
message
required
string
details

Optional, kind-specific.

object
key
additional properties
any
requestId
required

Server-assigned request id; also echoed via X-Request-Id header.

string
Examplegenerated
{
"error": {
"code": "example",
"message": "example",
"details": {},
"requestId": "example"
}
}

secret-write-conflict: with generateSecret, the trigger’s project already has a secret of that name (it’s never overwritten, and no trigger is left). Or the route-minted webhookId collided (astronomically rare).

Media typeapplication/json
object
error
required
object
code
required

Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).

string
message
required
string
details

Optional, kind-specific.

object
key
additional properties
any
requestId
required

Server-assigned request id; also echoed via X-Request-Id header.

string
Examplegenerated
{
"error": {
"code": "example",
"message": "example",
"details": {},
"requestId": "example"
}
}

The request body is over the API’s limit (request-too-large; details.maxBytes says how much).

Media typeapplication/json
object
error
required
object
code
required

Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).

string
message
required
string
details

Optional, kind-specific.

object
key
additional properties
any
requestId
required

Server-assigned request id; also echoed via X-Request-Id header.

string
Examplegenerated
{
"error": {
"code": "example",
"message": "example",
"details": {},
"requestId": "example"
}
}

Server error (unmapped domain code or framework crash).

Media typeapplication/json
object
error
required
object
code
required

Stable machine-readable discriminant. Values match domain error codes (see docs/API-ROUTE-CONVENTIONS.md §4.3).

string
message
required
string
details

Optional, kind-specific.

object
key
additional properties
any
requestId
required

Server-assigned request id; also echoed via X-Request-Id header.

string
Examplegenerated
{
"error": {
"code": "example",
"message": "example",
"details": {},
"requestId": "example"
}
}

See also