Skip to content

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 async function with the uniform signature described below, and
  • a config model — a pydantic.BaseModel subclass the host uses to validate the rule's handler_config before 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 — a PluginContext carrying the resolved project's identity plus resolved options.
  • credentials — the Integration's decrypted credential blob keyed by the manifest's CredentialField.name. Plugins that declare no credentials always receive {}.
  • external_identifier — the value the gateway resolved from IMPLEMENTED_BY.identifier_selector (e.g. a SonarQube /project/key JSON pointer); an empty string when not in play.
  • action_config — a pre-validated instance of the action's config_model, never a raw JSON string. Operators set the underlying handler_config on the rule; the host validates and constructs the model before dispatching.
  • event — the event context for the delivery, mirroring the project-independent fields of the Event row the host records: type (resolved event type, e.g. a GitHub X-GitHub-Event), integration (Integration slug), attributed_to (resolved Imbi user, '' when unattributed), metadata.headers (request headers, keys lower-cased and sensitive values redacted), and payload (the raw webhook body). config_model JSON-Pointer selectors resolve against this object, so the body lives under /payload; CEL expressions read payload.<field> (plus type, metadata, …). Most actions rely on ctx and external_identifier instead.

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). Action name must 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's callable and config_model import 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

actions() -> list[ActionDescriptor]

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
@classmethod
@abc.abstractmethod
def actions(cls) -> list[ActionDescriptor]:
    """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.
    """

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:

  1. Look the action up by name after parsing WebhookRule.handler as "<plugin_slug>#<action_name>".
  2. Resolve :attr:callable lazily via pydantic.ImportString and invoke it with the uniform :data:WebhookActionCallable signature.
  3. Resolve :attr:config_model to validate the rule's handler_config JSON blob before dispatching, and to surface :meth:pydantic.BaseModel.model_json_schema to 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.

WebhookActionCallable module-attribute

WebhookActionCallable = Callable[..., Awaitable[None]]