# Build Channels and Auth Providers

> Normalize a custom protocol into canonical events and authenticate ingress with bounded identity evidence.

## Before you begin {#before-you-begin}

Choose the protocol, Agent labels and selector, and customer identity mode. Use the platform Constal API Key provider for Constal keys; write an Auth Provider only for external proof such as signed webhooks or third-party bearer tokens.

## Steps {#steps}

1. Define authentication. Return only a stable subject, optional downstream customer external id, bounded claims, and optional expiry:

   ```ts
   import { authProvider } from "@constal/sdk";

   export default authProvider({
     id: "signed-webhook", version: "1.0.0",
     needs: [{ binding: "verifier", kind: "service", ops: ["verify"] }],
     async authenticate({ request, ingress }, context) {
       const proof = await context.invoke<{ valid: boolean; subject?: string }>(
         context.resources.verifier!, "verify", { ingress, headers: request.headers, bodyBase64: request.bodyBase64 },
       );
       return proof.valid && proof.subject
         ? { authenticated: true, subject: proof.subject }
         : { authenticated: false, reason: "invalid signature" };
     },
   });
   ```

2. Define the Channel after the Auth Provider has a stable CRN:

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

   export default channel({
     id: "webhook", version: "1.0.0", public: true,
     authProvider: authProviderName("crn:constal:production:acme:default:auth-provider/signed-webhook"),
     protocol: {
       id: "json-webhook", version: "1",
       receive(request) {
         const body = JSON.parse(atob(request.bodyBase64 ?? ""));
         return { id: body.id, type: "message", session: body.session, data: body.message };
       },
       respond(result) {
         return { status: result.status === "failed" ? 500 : 200,
           headers: { "content-type": "application/json" }, bodyBase64: btoa(JSON.stringify(result)) };
       },
     },
   });
   ```

3. Set the Agent `target` Resource selector and `customerIdentity` in the manifest. A public selector must positively match a `channels.constal.ai/*` label declared by the Agent. Central authentication maps provider evidence to accepted authority before `receive`. The same Auth Provider contract receives an exact `{ kind, crn }` ingress target for Channels and authenticated UIs; it is not a second UI authentication API. Use `ChannelContext.invoke()` for declared external work; Channel and Auth Provider code never receives Credential bytes.

## Verify {#verify}

Send one invalid and one valid signed request to `/v1/channels/:tenant/:namespace/:channel/*`. Invalid proof must fail before `receive`; valid proof must resolve only an Agent matched by the Channel selector. Confirm the Run pins the Channel revision, selector hash, and selected Agent revision. Repeat the delivery id and confirm the recorded outcome is reused.

## Next steps {#next-steps}

Read [Deploy a Channel](/docs/channels/deploy.md), [Deploy an Auth Provider](/docs/channels/auth-providers/deploy.md), and [Operate Channels](/docs/channels/operate.md) for packaging, ingress, messages, and events.
