# 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, target Agent, 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 }, context) {
       const proof = await context.invoke<{ valid: boolean; subject?: string }>(
         context.resources.verifier!, "verify", { 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, resourceName } 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"),
     target: resourceName({ environment: "production", tenant: "acme", namespace: "default", kind: "agent", path: "support" }),
     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 `customerIdentity` in the manifest to `required`, `optional`, or `none`. Central authentication maps provider evidence to accepted authority before `receive`. 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 produce the expected source, target, principal, and customer partition. 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, events, and alarms.
