Webhook Actions Capability¶
WebhookActionsCapability is the contract base for a capability that
reacts to inbound webhook payloads. Bind it with a
Capability(kind='webhook-actions', handler=...) in the plugin's
manifest. The host (typically imbi-gateway) receives the webhook,
resolves the matching project(s) from the payload, and routes each match
to a named action.
Surfaces: webhook.
See Authoring Plugins for the manifest, capabilities, context, credential decryption, and error conventions shared by every plugin.
The action-catalog contract¶
A webhook-actions capability does not implement a dispatch method.
Instead it advertises a static catalog of actions via the actions()
classmethod, returning one ActionDescriptor per action. Each descriptor
names the action and points (via pydantic.ImportString) at:
- a callable — a module-level
asyncfunction with the uniform signature described below, and - a config model — a
pydantic.BaseModelsubclass the host uses to validate the rule'shandler_configbefore dispatch and to render the rule editor from its JSON Schema.
The host parses WebhookRule.handler as "<plugin_slug>#<action_name>",
looks the plugin up in the registry, selects the matching descriptor,
validates handler_config against config_model, and invokes
callable. The capability class itself carries no runtime dispatch
logic.
# imbi.plugins.sonarqube/capabilities.py
from imbi.common.plugins import (
ActionDescriptor,
WebhookActionsCapability,
)
class SonarQubeWebhookActions(WebhookActionsCapability):
@classmethod
def actions(cls) -> list[ActionDescriptor]:
return [
ActionDescriptor(
name='update_project_from_webhook',
label='Update project from webhook',
description='Pull the latest analysis onto the project.',
callable='imbi.plugins.sonarqube.actions'
':update_project_from_webhook',
config_model='imbi.plugins.sonarqube.actions:UpdateConfig',
),
]
# imbi.plugins.sonarqube/actions.py
import pydantic
from imbi.common.plugins import PluginContext
class UpdateConfig(pydantic.BaseModel):
metric_keys: list[str] = ['coverage', 'bugs']
async def update_project_from_webhook(
*,
ctx: PluginContext,
credentials: dict[str, str],
external_identifier: str,
action_config: UpdateConfig,
event: object,
) -> None:
...
The callable signature¶
The host invokes the action callable with keyword-only arguments:
ctx— aPluginContextcarrying the resolved project's identity plus resolved options.credentials— the Integration's decrypted credential blob keyed by the manifest'sCredentialField.name. Plugins that declare no credentials always receive{}.external_identifier— the value the gateway resolved fromIMPLEMENTED_BY.identifier_selector(e.g. a SonarQube/project/keyJSON pointer); an empty string when not in play.action_config— a pre-validated instance of the action'sconfig_model, never a raw JSON string. Operators set the underlyinghandler_configon the rule; the host validates and constructs the model before dispatching.event— the event context for the delivery, mirroring the project-independent fields of theEventrow the host records:type(resolved event type, e.g. a GitHubX-GitHub-Event),integration(Integration slug),attributed_to(resolved Imbi user,''when unattributed),metadata.headers(request headers, keys lower-cased and sensitive values redacted), andpayload(the raw webhook body).config_modelJSON-Pointer selectors resolve against this object, so the body lives under/payload; CEL expressions readpayload.<field>(plustype,metadata, …). Most actions rely onctxandexternal_identifierinstead.
Rules of thumb¶
- Keep one action per public verb (
update_project_from_webhook,notify_release, …) rather than overloading a single action with branching config. Action names become part of the operator-facing rule string (sonarqube#update_project_from_webhook). Actionnamemust match^[a-z][a-z0-9_]*$and be unique within the plugin; the registry rejects duplicates. - Treat the host's "best effort" guarantee seriously: a call may run
after a related
events-table insert has failed, and the host will not retry on its own. Make actions idempotent so manual rerun is safe. - Return a fresh list from
actions()each call so callers can mutate the result safely. The host validates each descriptor'scallableandconfig_modelimport strings at registry-load time, so misconfigured paths fail loud during load rather than at request time.
Hints¶
cacheable— the host may cache reads from this capability.
API reference¶
WebhookActionsCapability ¶
Bases: CapabilityHandler
Declares a static catalog of gateway-dispatched actions.
The host (imbi-gateway) parses WebhookRule.handler as
"<plugin_slug>#<action_name>", looks the plugin up in the
registry, picks the matching :class:ActionDescriptor, validates the
rule's handler_config against
:attr:ActionDescriptor.config_model, and calls
:attr:ActionDescriptor.callable with the uniform signature captured
in :data:WebhookActionCallable. The capability itself carries no
runtime dispatch logic — the callable lives wherever the descriptor
points.
actions
abstractmethod
classmethod
¶
Return the static catalog of actions this capability exposes.
Implementations should return a fresh list each call so callers
can mutate the result safely. The host validates each descriptor's
callable and config_model ImportStrings at registry load,
so misconfigured paths fail loud there rather than at request time.
Source code in libraries/common/src/imbi/common/plugins/base.py
ActionDescriptor ¶
Bases: BaseModel
Describes a single action exposed by a webhook-actions capability.
A :class:WebhookActionsCapability returns a list of these from
:meth:~WebhookActionsCapability.actions so the host can:
- Look the action up by
nameafter parsingWebhookRule.handleras"<plugin_slug>#<action_name>". - Resolve :attr:
callablelazily viapydantic.ImportStringand invoke it with the uniform :data:WebhookActionCallablesignature. - Resolve :attr:
config_modelto validate the rule'shandler_configJSON blob before dispatching, and to surface :meth:pydantic.BaseModel.model_json_schemato the rule editor UI.
label and description are operator-facing text. The UI pairs
them with the JSON Schema derived from config_model to render
the rule editor; no additional per-field metadata is needed on the
descriptor itself because pydantic Field(...) annotations on
the config model fields already flow through to the schema.