Deploy a Channel

Build and activate an immutable Channel package from an archive or pinned Git commit.

Before you begin

Prepare a ZIP or TAR.GZ package, or a public HTTPS repository pinned to a full commit SHA. The Channel manifest declares its Auth Provider, Kubernetes-style Agent selector, public visibility, customer-identity requirement, bindings, Policies, and runtime compatibility. Public selectors must positively match a channels.constal.ai/* Agent label, so deployment alone cannot accidentally expose an Agent.

Steps

  1. Open Channels and choose Deploy Channel.
  2. Select an archive or pinned Git source.
  3. Supply the package information and review the deployment guide.
  4. Choose Deploy.
  5. Wait while Constal builds the executable, validates the immutable Channel identity, verifies it, and activates the new revision.
  6. Open the Channel detail page.

The entrypoint exports a validated protocol definition:

ts
import { channel } from "@constal/sdk";

export default channel({
  id: "support-webhook", version: "1.0.0", public: true,
  authProvider: { kind: "local", resourceKind: "auth-provider", id: "signed-webhook" },
  protocol: {
    id: "acme.support-webhook", version: "1",
    receive(request) {
      return { id: request.headers["x-delivery-id"] ?? "", type: "message",
        session: request.headers["x-session-id"] ?? "", data: { bodyBase64: request.bodyBase64 } };
    },
    respond(result) {
      return { status: result.status === "complete" ? 200 : 202,
        headers: { "content-type": "application/json" },
        bodyBase64: btoa(JSON.stringify(result)) };
    },
  },
});

The manifest owns routing independently of protocol code:

constal.channel.json
{
  "schemaVersion": 2,
  "kind": "channel",
  "id": "support-webhook",
  "namespace": "default",
  "version": "1.0.0",
  "entry": "src/index.ts",
  "public": true,
  "customerIdentity": "none",
  "authProvider": { "kind": "local", "resourceKind": "auth-provider", "id": "signed-webhook" },
  "target": {
    "resourceKind": "agent",
    "selector": { "matchLabels": { "channels.constal.ai/support-webhook": "enabled" } }
  },
  "bindings": {},
  "policies": [],
  "config": {}
}

The common POST /v1/deployments endpoint accepts the raw Channel archive with deployment authority and a stable idempotency key. Channel configuration and SDK code never contain reusable Credential material.

Use a local dependency for a companion Resource installed into the same tenant and namespace. Constal resolves it during deployment and pins the exact CRN and hash into the immutable Channel Resource. Use an exact CRN only when the dependency is intentionally global or already pinned outside the package graph; never copy the package publisher's tenant into a reusable manifest.

Deployment does not mutate prior revisions. Requests already admitted under an older deployment retain their exact recorded identity.

Shared provider subscriptions

An external account or GitHub App installation is a source, not a Constal tenant. Each tenant installs its own Channel and subscribes it to that source through POST /v1/namespaces/:namespace/channels/install. Multiple tenant Channels can subscribe to the same source; an existing subscription is not evidence that another tenant is authorized.

A shared source is an ordinary Channel with a target such as { "resourceKind": "channel", "subscriptions": { "provider": "example" } }. Its AuthProvider authenticates the callback; receive returns { id, source: { provider, key }, request }. The host forwards the delivery to each authorized subscriber. Shared source Channels are registered in the platform's ingress namespace, using their provider ID. Source provenance pins the Channel, AuthProvider, and request digest and survives retries. Subscriber AuthProviders receive this as context.source; HTTP callers cannot supply it. This allows subscriber authentication without disclosing the source signing Credential.

The source Channel also implements protocol.authorizeSubscription(request, context). Each installation route may supply credentials: [{ crn, hash }]. The host checks the caller's Credential read/use permissions and current revisions, asks the pinned Credential Providers for fresh verification evidence, and passes the resulting bounded claims to the source handler. The handler checks issuer provenance and external access before any Channel, binding, or subscription changes. Other outbound bindings do not need to provide subscription evidence.

scopedBindings uses the ordinary binding owner. For app automation, declare { "key": "github-app", "owner": { "kind": "tenant" }, "target": { "crn": "…", "hash": "…" } }. The selected Credential is an authorized existing Resource. A personal binding instead belongs to an explicit principal. The webhook's verified sender becomes the Channel's tenant-local principal; it never implicitly becomes the installer. Horizon's GitHub automation uses the app binding explicitly.

Verify

Confirm the detail page shows the endpoint, selector hash, selector, and currently matched Agents. Send a controlled test message and verify both the delivery receipt and the resulting Run's pinned Channel admission.

Next steps

Continue with Operate Channels and Start a Run.

Shared webhook subscriptions

A shared callback can be implemented as a source Channel with target { "resourceKind": "channel", "subscriptions": { "provider": "example" } }. Its AuthProvider verifies the incoming request before receive returns { id, source: { provider, key }, request }. Source Channels are registered in platform/ingress with the provider as their ID.

The source's authorizeSubscription handler receives the subscriber's configuration and host-verified Credential evidence. Channel installation supplies the relevant Credential references through each ingressRoutes entry. The host saves those references independently of subscriber-authored code and checks current access before sending payloads to a tenant inbox or invoking tenant authentication. Sources should identify the smallest independently authorized stream, such as an installation and repository together.

Multiple Channels and tenants can subscribe to the same source. Each gets an independent inbox and delivery progress; revoked access or a failed recipient does not prevent other recipients from receiving the event. Tenant AuthProviders can use host-supplied context.source to identify the verified origin without receiving a shared signing key.