Custom hook action
Custom hook action
Inline hooks are built on small contracts you can implement.
Write an in-process action
Implement Cbox\Id\ExternalActions\Contracts\Action and list it in config. It runs
synchronously at its hook point:
interface Action
{
public function handle(ActionContext $context): ActionResult;
}
ActionContext::string($key)reads the point's payload (fortoken_minting:client_id,subject,user_id,organization_id,grant, plusscopes/claims). Every point's shape is listed under Hook points.- Return
ActionResult::continue([...])to allow (optionally with enrichment) orActionResult::deny($reason)to veto. - Actions are resolved from the container, so constructor-inject whatever you need.
- Registration is deny-by-default: only classes listed in
cbox-id.external_actions.hooks.<point>run.
Swap how external endpoints are called
The transport that makes the outbound HTTP call is behind
Cbox\Id\ExternalActions\Contracts\ActionTransport:
interface ActionTransport
{
public function send(ExternalActionEndpoint $endpoint, ActionContext $context): ActionResult;
}
The default HttpActionTransport is SSRF-guarded, signed, short-timeout, no-retry and
fails closed. Rebind it to change transport behaviour (a different signing scheme, mTLS,
a message-queue bridge) while keeping the pipeline and fail-closed semantics:
$this->app->singleton(ActionTransport::class, MyMtlsActionTransport::class);
If you rebind it, preserve the guarantees callers rely on: never follow redirects,
keep TLS verification on, and return a result — never throw — on failure. For a failure,
resolve FailPolicy::for($context->hookPoint) rather than hardcoding a deny: that is what
keeps a gate closed while letting a login through when the host asked for that (see
Hook points).
Implement ConcurrentActionTransport as well if your transport can call several endpoints
at once — the pipeline falls back to one-at-a-time sends without it, and with it comes the
old additive cost of one timeout per endpoint on the auth path.
Test with the shipped fake
Cbox\Id\ExternalActions\Testing\InteractsWithExternalActions gives you
fakeActionTransport() (an in-memory transport you script with willEnrich() /
willDeny() and assert with assertSent()), so the pipeline and your hook are testable
without touching the network.
See Security: external actions for the invariants.