# Take over a webhook trigger

POST

/v1/webhooks/{triggerId}/owner

```sh
curl --request POST \
  --url https://example.com/v1/webhooks/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/owner \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{ "runAsServiceAccountId": "example", "runAsProject": true }'
```

The caller becomes the trigger’s owner, so its runs act as the caller from the next delivery. Needs `admin` on the trigger’s project (without an authorization store, a tenant-admin key) and `execute` on its flow. For a trigger whose owner left or lost access, or to make one shared or personal. It’s the one way a trigger’s owner changes, audited as `trigger-owner-set`, naming the trigger’s version after it; a person it’s taken from is told, where the server tells owners. Name `runAsServiceAccountId` instead to make a service account the owner: that needs `act_as` on the account (a tenant admin’s), and the account must itself hold `execute` on the flow. With an authorization store, that’s checked when the account is named: an account that can’t is refused (`409 trigger-owner-cannot-run-target`). Or name `runAsProject: true` to make the project’s own service account the owner, made the first time with the `editor` role on the project, so the trigger is shared and runs as the project. It runs only what the project’s editors can, and is checked the same way. The trigger keeps its `toolsAllowedUnattended`, now for the new owner’s runs; a body naming it is refused (change it with PATCH).

## Authorizations

- **[bearerAuth](https://docs.kindgi.com/v0.3.1/reference/api.md#bearerauth)**

## Parameters

### Path Parameters

**triggerId**

required

string format: uuid

TriggerId — the trigger id (a UUID).

### Header Parameters

**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). A request whose key store can’t be read before it runs is answered `503 idempotency-store-unavailable` with `Retry-After`: nothing ran, so retry with the same key (from 0.3.0).

## Request Body

application/json

Who the trigger’s runs act as from now: for a trigger whose owner left, or to make one shared or personal. Absent, or an empty body: the caller takes it over (today’s behaviour). With `runAsServiceAccountId`: that service account becomes the owner. With `runAsProject`: the project’s own account does. A take-over keeps the trigger’s `toolsAllowedUnattended`: change that with PATCH.

object

**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 what the trigger runs. With an authorization store, that’s checked when the account is named: an account that can’t is refused (`409 trigger-owner-cannot-run-target`). Not a project’s own account (409 `service-account-project-owned`: name it with `runAsProject`).

string

\>= 1 characters

**runAsProject**

`true`: the trigger’s runs act as its project’s own service account, made the first time with the `editor` role on the project, so it’s shared. It runs only what the project’s editors can. With an authorization store, that’s checked when it’s named: if it can’t run what the trigger runs, the request is refused (`409 trigger-owner-cannot-run-target`). Takes `admin` on the trigger’s project (without an authorization store, a tenant-admin key), not `act_as`. Not with `runAsServiceAccountId`, and not without a project (400). A runtime that can’t make the account answers 501 `trigger-operation-unsupported`. `false` or absent: as `runAsServiceAccountId` says.

boolean

##### Examplegenerated

```json
{
  "runAsServiceAccountId": "example",
  "runAsProject": true
}
```

## Responses

### 200

The trigger, with its new owner.

application/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. A person: a personal trigger, which only they or a project admin change. A service account: a shared trigger. Only a take-over (`POST …/owner`) changes it. 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

**isProjectAccount**

Set when the owner is the trigger’s project’s own service account (`runAsProject`): the trigger is shared, and runs as the project. Show it as “This project”. Absent otherwise.

boolean

**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:

- **object**

  One of:

  - **object**

    object

    **kind**

    required

    string

    Allowed value: tenant

  - **object**

    object

    **kind**

    required

    string

    Allowed value: org

    **orgId**

    required

    string format: uuid

  - **object**

    object

    **kind**

    required

    string

    Allowed value: project

    **projectId**

    required

    string format: uuid

- **null**

  null

**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**

  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

- **object**

  object

  **kind**

  required

  string

  Allowed values: standard-webhooks

  **toleranceSeconds**

  integer

  \>= 1 <= 3600

**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

**version**

The trigger’s newest version (`GET …/versions`): one more for each change to what it runs, when, as whom or with what. Absent until its first version.

integer

\>= 1

**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

**toolsAllowedUnattended**

The tools whose calls in this trigger’s runs don’t wait for a person’s yes, though a tool asks for one in a run no person started (`needsApprovalWhenUnattended`). Absent: none. Each tool by its id, with no wildcard or pattern, and only tools this trigger’s flow or agent calls. It lifts only that wait: the tenant’s approval rules, the agent’s own rules, the permission check (as the trigger’s owner) and the tool’s own limits all still apply.

Array\<string>

<= 100 items unique items

**lastFiredAt**

required

string | null format: date-time

**createdAt**

required

string format: date-time

**updatedAt**

required

string format: date-time

##### Example

```json
{
  "owner": {
    "kind": "user",
    "isProjectAccount": true
  },
  "hmacSecretScope": {
    "kind": "tenant"
  },
  "signature": {
    "kind": "hmac-sha256",
    "encoding": "hex"
  },
  "status": "active"
}
```

### 400

A malformed body, or `runAsProject` with `runAsServiceAccountId`, or a body naming `toolsAllowedUnattended` (`bad-input`).

application/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

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 401

Missing / malformed / expired / revoked bearer token.

application/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

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 403

`permission-denied`: not an admin of the trigger’s project, or no `execute` on its flow, or no `act_as` on the account named.

application/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

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 404

No webhook trigger with that id, or no such service account.

application/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

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 409

`runAsServiceAccountId` names an unregistered account (`service-account-unregistered`), a project’s own account (`service-account-project-owned`: name it with `runAsProject`), or one that can’t run the flow (`trigger-owner-cannot-run-target`). Or, with `runAsProject`, the project’s own account can’t run the flow either (`trigger-owner-cannot-run-target`; the account stays, for the next `runAsProject`), or an active account someone made already has the project account’s name, `project-<projectId>` (`service-account-name-taken`: a tenant admin can unregister it). Or an idempotency conflict.

application/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

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 413

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

application/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

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 500

Server error (unmapped domain code or framework crash).

application/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

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 501

`trigger-operation-unsupported`: this deployment can’t change a trigger’s owner, or can’t make a project’s own account (`runAsProject`).

application/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

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 503

The `Idempotency-Key` store couldn’t be read before the request ran (`idempotency-store-unavailable`), so nothing was changed; `Retry-After` (whole seconds) says when to try again with the same key.

application/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

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

## See also

- [`GET /v1/webhooks/{triggerId}`: Fetch a webhook trigger](https://docs.kindgi.com/v0.3.1/reference/api/operations/webhooksget.md)
- [`PATCH /v1/webhooks/{triggerId}`: Update a webhook trigger](https://docs.kindgi.com/v0.3.1/reference/api/operations/webhooksupdate.md)
- [`POST /v1/webhooks/{triggerId}/pause`: Pause a webhook trigger](https://docs.kindgi.com/v0.3.1/reference/api/operations/webhookspause.md)
- [`POST /v1/webhooks/{triggerId}/resume`: Resume a webhook trigger](https://docs.kindgi.com/v0.3.1/reference/api/operations/webhooksresume.md)
- [Error code `bad-input`](https://docs.kindgi.com/v0.3.1/reference/errors.md#bad-input)
- [Error code `idempotency-store-unavailable`](https://docs.kindgi.com/v0.3.1/reference/errors.md#idempotency-store-unavailable)
- [Error code `permission-denied`](https://docs.kindgi.com/v0.3.1/reference/errors.md#permission-denied)
- [Error code `request-too-large`](https://docs.kindgi.com/v0.3.1/reference/errors.md#request-too-large)
- [Error code `service-account-name-taken`](https://docs.kindgi.com/v0.3.1/reference/errors.md#service-account-name-taken)
- [Error code `service-account-project-owned`](https://docs.kindgi.com/v0.3.1/reference/errors.md#service-account-project-owned)
- [Error code `service-account-unregistered`](https://docs.kindgi.com/v0.3.1/reference/errors.md#service-account-unregistered)
- [Error code `trigger-operation-unsupported`](https://docs.kindgi.com/v0.3.1/reference/errors.md#trigger-operation-unsupported)
- [Error code `trigger-owner-cannot-run-target`](https://docs.kindgi.com/v0.3.1/reference/errors.md#trigger-owner-cannot-run-target)
- [Every error code](https://docs.kindgi.com/v0.3.1/reference/errors.md)
