Gateway¶
imbi-gateway receives webhooks from external systems, for example GitHub,
PagerDuty, and SonarQube. For each delivery, it finds the Imbi projects that
the delivery is about, records the delivery, and runs the rules of the
webhook. A rule calls an action, for example "create a release" or
"start an agent task".
The gateway runs on port 8003. The all-in-one image serves it under
/gateway.
The webhook URL¶
Each Webhook has its own URL:
The Notification Path on the webhook detail page shows /<webhook id>.
The endpoint has no bearer check. To prove that a delivery is genuine, use a
signing secret (see Signature verification).
The body must be JSON.
Status codes¶
| Code | Meaning |
|---|---|
202 Accepted |
One or more rules matched, and the gateway ran their actions. An action that fails does not change the code. The webhook history shows the result of each action |
204 No Content |
The gateway accepted the delivery and did nothing. See the list below |
422 Unprocessable Content |
The body is not JSON |
500 Internal Server Error |
The webhook belongs to more than one organization |
The gateway returns 204 when one of these is true:
- No webhook has this id.
- The webhook has no integration.
- The signature is missing or does not verify.
- The Identifier Selector does not resolve in the body.
- No project has that identifier for the integration.
- The
webhook-actionscapability of the integration is off. The gateway records the delivery, but runs no rule. - No rule matches.
A scheduled task with a gateway target records 204 as no_effect. See
Scheduled Tasks.
How the gateway processes a delivery¶
- It reads the Webhook, its organization, its integration, and its rules.
- For a GitHub integration, it verifies the signature when the webhook has a secret.
- It reads the project identifier from the body with the Identifier Selector.
- It finds each project whose identifier for the integration has that value. More than one project can match. For PagerDuty, it verifies the signature against the secret of each matched project.
- It finds the Imbi user who caused the event, when it can (see Attribution).
- It resolves the event type with the Event Type Selector.
- It records one
eventsrow for each matched project. The row goes to the Iggy streamevents, topicgateway, and the sink writes it to the ClickHouseeventstable. - It evaluates the filter expression of each rule. See Webhook Rule Filter Expressions.
- For each matched project, it runs each matched rule in rule order.
- It records the outcome of each action (
succeeded,failed, orskipped) on theeventsrow.
The Webhook History of a webhook shows these rows.
Set up a webhook¶
Do these steps one time for each source.
- Make sure that the integration has the
webhook-actionscapability on. See Integrations and Plugins. - Make sure that each project has its identifier for the integration, for example the GitHub repository id.
- Open Admin > \
Admin > Webhooks and create a webhook. See Webhooks for the form. -
Set the fields of the integration binding:
Field What to enter Integration The integration that sends the webhook Identifier Selector A JSON pointer into the body, for example /repository/idUser Subject Selector A JSON pointer to the id of the external user, for example /sender/idUser Type Selector Optional. A JSON pointer to the account type, for example /sender/type. A value ofBotskips the user lookupIdentity Integration Slug Optional. The integration that maps the external user to an Imbi user Event Type Selector Optional. A header name, for example x-github-event, or a JSON pointer that starts with/Secret The signing secret. For GitHub, use the secret of the GitHub hook -
Add the rules. Each rule has a Filter Expression (CEL), a Handler, and a Handler Config (JSON). Rules run in the order of the list.
- Save the webhook. Copy its URL into the source system.
The selectors of the binding read the body directly (/repository/id). The
selectors in a handler_config read the event context, so the body is
under /payload (/payload/repository/id).
The person who saves the rules is accountable for them. An agent task that a rule makes belongs to that person.
Signature verification¶
| Source | Header | Secret | Prefix |
|---|---|---|---|
| GitHub | X-Hub-Signature-256 |
The Secret of the webhook | sha256= |
| PagerDuty | X-PagerDuty-Signature |
The secret on the identifier of the project | v1= |
Each plugin declares its signature with WebhookSignature (the
signature() class method of its webhook-actions capability). The table
shows the first-party plugins. A plugin that declares no signature gets no
verification.
The signature is an HMAC-SHA256 of the raw body. The header can hold more than one signature, separated by commas. One match is sufficient.
- When a secret is set, a delivery with a missing or wrong signature gets
204, and the gateway records nothing. - When a GitHub webhook has no secret, the gateway processes its deliveries without verification. It logs a warning one time for each webhook.
The filter variable github_signature_verified is true only for a GitHub
delivery that verified. The actions create_agent_task and start_workflow
run only for a signed delivery: a GitHub delivery that verified, or a
delivery to a project whose identifier has a secret.
Attribution¶
The gateway reads the external user id with the User Subject Selector.
It then asks imbi-api for the Imbi user with that identity. It asks the
Identity Integration Slug when you set it. Otherwise it asks the
webhook's own integration, when that integration has the identity
capability on.
The resolved user goes into attributed_to, and into created_by of a
release that an action makes. When the gateway finds no user, or two
different users, it leaves the delivery unattributed.
Handlers¶
A rule's Handler has the form <plugin slug>#<action name>, for
example gateway-actions#create_release. Each part starts with a
lowercase letter. After that, the plugin slug uses lowercase letters,
digits, and hyphens, and the action name uses lowercase letters, digits,
and underscores.
imbi-api checks the handler when you save the rule. It refuses a handler
in another form, and a plugin or action that is not in its plugin
registry. imbi-api does not load the gateway-actions plugin, so it
checks only the form of a gateway-actions handler.
The gateway resolves the handler again when a delivery arrives. When the
handler has another form, the plugin or the action does not exist, or the
handler_config is not valid, the gateway logs an error and skips that
rule. The other rules of the webhook still run.
When a plugin declares credentials, its actions get the credentials of the webhook's integration. When the integration has no credentials, the gateway skips the rule.
To write a new action, see Webhook Actions Capability.
Built-in actions¶
The gateway includes the plugin gateway-actions. It needs no credentials.
| Handler | What it does |
|---|---|
gateway-actions#update_project |
Patches the project with values from the delivery |
gateway-actions#create_release |
Creates the release, or confirms that it exists |
gateway-actions#add_deployment_event |
Records a deployment status for an environment |
gateway-actions#publish_release |
Publishes the release on a successful deployment |
gateway-actions#block_release |
Blocks the release on a failed deployment |
gateway-actions#update_release_drift |
Sends a push of the drift notes ref to imbi-api |
gateway-actions#ingest_sbom |
Stores a CycloneDX SBOM on a release |
gateway-actions#create_agent_task |
Starts an agent task on the project |
gateway-actions#start_workflow |
Starts a workflow run |
In the tables below, a pointer is a JSON pointer into the event context,
and a CEL field is a CEL expression over the event context. The webhook
body is /payload in a pointer and payload in CEL. CEL fields in a
handler_config can use substring(). See
Webhook Rule Filter Expressions.
update_project¶
handler_config is a list. Each item adds one value to the project with a
JSON Patch add operation.
| Field | Type | Required | What it is |
|---|---|---|---|
path |
JSON pointer | Yes | The location on the Imbi project |
from |
pointer | Yes | The value to read from the delivery |
create_release¶
| Field | Type | Default | What it is |
|---|---|---|---|
committish_expression |
CEL | none | The commit of the release, as 7 lowercase hex characters |
version_expression |
CEL | none | The tag of the release |
title_selector |
pointer | none | The title. When it is not set or is null, the title is Release <tag>, or Release <commit> when there is no tag |
You must set committish_expression, version_expression, or both. When
neither resolves, the action does nothing. imbi-api finds the commit of a
tag when you set only version_expression.
The create is idempotent. Two rules that announce the same release make one release.
{
"committish_expression": "substring(payload.deployment.sha, 0, 7)",
"version_expression": "payload.deployment.ref",
"title_selector": "/payload/deployment/description"
}
add_deployment_event¶
| Field | Type | Default | What it is |
|---|---|---|---|
environment_selector |
pointer | required | The environment slug |
committish_expression |
CEL | required | The commit of the deployment |
status_selector |
pointer | required | The deployment state. See the state mapping |
version_expression |
CEL | none | The tag. The action finds the release by tag first, then by commit |
note_selector |
pointer | none | A note for the event |
external_run_id_selector |
pointer | none | The id of the external run. imbi-api uses it to update the event that the deploy flow already recorded |
external_run_url_selector |
pointer | none | The URL of the external run. A null or empty value is ignored |
When no release matches, the action records the deployment on the project
and environment without a release. The deployment-sweep maintenance
operation attaches the release later. A state that is not in the mapping is
skipped.
{
"environment_selector": "/payload/deployment/environment",
"committish_expression": "substring(payload.deployment.sha, 0, 7)",
"version_expression": "payload.deployment.ref",
"status_selector": "/payload/deployment_status/state",
"external_run_url_selector": "/payload/deployment_status/log_url"
}
publish_release¶
| Field | Type | Default | What it is |
|---|---|---|---|
version_expression |
CEL | required | The tag of the release |
status_selector |
pointer | required | The deployment state |
prerelease |
boolean | false |
Mark the remote release as a prerelease |
The action runs only when the state maps to success. It makes the remote
release for the tag, for example the GitHub Release. When imbi-api has no
release for the tag (404), or the release is blocked (409), the action
does nothing.
block_release¶
| Field | Type | Default | What it is |
|---|---|---|---|
version_expression |
CEL | required | The tag of the release |
status_selector |
pointer | required | The deployment state |
reason_selector |
pointer | none | The reason. The default is Deployment reported <state>. Imbi keeps 500 characters |
The action runs only when the state maps to failed (failure or error).
The state inactive does not block a release.
update_release_drift¶
| Field | Type | Default | What it is |
|---|---|---|---|
ref |
string | refs/notes/imbi-drift |
The notes ref that carries the drift verdicts |
Use it with a GitHub push delivery. The action skips a push to any other
ref, and a push without a full before and after commit. It sends before
and after to imbi-api, which reads the changed notes and marks the
releases. See Drift.
ingest_sbom¶
| Field | Type | Default | What it is |
|---|---|---|---|
version_expression |
CEL | required | The tag of the release |
sbom_selector |
pointer | /payload |
The CycloneDX 1.7 document |
committish_expression |
CEL | none | When set, the action creates the release if no release has the tag. Imbi keeps the first 7 hex characters |
title_selector |
pointer | none | The title of a release that the action creates |
When no release has the tag and committish_expression is not set, the
action drops the SBOM and logs a warning.
{
"version_expression": "payload.ref_name == \"main\" ? substring(payload.sha, 0, 7) : payload.ref_name",
"sbom_selector": "/payload/sbom",
"committish_expression": "payload.sha"
}
create_agent_task¶
| Field | Type | Default | What it is |
|---|---|---|---|
agent_slug |
string | required | The agent that does the task |
title_expression |
CEL | required | The title. For a fixed text, use a quoted CEL string |
description_expression |
CEL | required | The description |
delivery_id_selector |
pointer | /metadata/headers/x-github-delivery |
The id of the delivery |
The action runs only for a signed delivery with a valid delivery id. A redelivery makes no second task. See Agent Tasks.
start_workflow¶
| Field | Type | Default | What it is |
|---|---|---|---|
workflow_slug |
string | required | The workflow |
trigger |
string | required | The webhook trigger of the workflow |
binding |
string | required | The id of the trigger binding |
delivery_id_selector |
pointer | /metadata/headers/x-github-delivery |
The id of the delivery |
Do not write this rule. imbi-workflows writes it when you activate a workflow with a webhook trigger. The action runs only for a signed delivery with a valid delivery id. See Workflows.
Plugin actions¶
Other plugins add actions. These are the actions of the plugins in this repository:
| Handler | What it does | handler_config |
|---|---|---|
github#sync_commits |
Gets the commits of a push and records them | Optional pointers: before_selector, after_selector, ref_selector, repository_selector, repo_api_url_selector. Optional api_base_url |
github#sync_tags |
Records the pushed tag | As sync_commits, without before_selector. Optional reconcile_all (default false) records all tags |
github#sync_pull_requests |
Records pull_request events |
Optional pointers: action_selector, pr_selector, repository_selector, repo_api_url_selector. Optional api_base_url |
github#sync_pull_request_reviews |
Records pull_request_review events |
Optional pointers: action_selector, review_selector, pr_selector |
sonarqube#update_project_from_webhook |
Gets SonarQube measures and patches the project | A list of {"metric": "coverage", "path": "/<project pointer>"} |
The pointer defaults read the GitHub payload, for example
/payload/repository/full_name. For each plugin, see
GitHub and SonarQube.
Settings¶
The full list is in Configuration. The gateway needs these:
| Variable | What it is |
|---|---|
POSTGRES_URL |
The graph, for webhooks, rules, integrations, and projects |
CLICKHOUSE_URL |
ClickHouse. The gateway does not start when it cannot connect |
IGGY_URL |
Iggy, for the events rows. The gateway does not start when it cannot connect |
IMBI_AUTH_ENCRYPTION_KEY |
Decrypts webhook secrets and integration credentials. Use the same value as imbi-api |
IMBI_GATEWAY_API_TOKEN |
The ik_ API key of the imbi-gateway service account |
IMBI_INTERNAL_API_URL |
Where the gateway connects to imbi-api, for example http://imbi-api:8000 |
IMBI_API_URL |
The public URL of imbi-api. The gateway uses its path, for example /api, as the route prefix. It must be an absolute URL |
The service account¶
The actions call imbi-api as the imbi-gateway service account. Its role,
also imbi-gateway, grants project:read, project:write, user:read,
agent_task:create, and workflow:run.
imbi-api setupmakes the account and its API key. WhenIMBI_GATEWAY_API_TOKENis set, setup uses that value. When it is not set, setup prints a new key one time.- In the all-in-one image (
IMBI_SERVICE=all), the entrypoint makes the key when the variable is not set. - The account must be a member of each organization that has webhooks, with
the
imbi-gatewayrole. For another organization, runimbi-api setup-service-accounts --organization <organization slug>.
Endpoints¶
| Path | What it is |
|---|---|
POST /notifications/{webhook_id} |
The webhook endpoint. Public as /gateway/notifications/{webhook_id} |
GET /status |
The health check. Public as /gateway/status |
GET /metrics |
Prometheus metrics. The proxy refuses /gateway/metrics, so scrape the pod |
The counter imbi.gateway.deployment_events counts the outcome of each
add_deployment_event call.