This document is maintained with the Channel App SDK. Check the SDK README first for current packages and the complete reading order. The source of truth for this Document is the GitHub source.
A Channel app consists of a server that executes Functions and, when needed, a web UI built as a WAM. Extensions connect related Functions to standard Channel capabilities, while AppStore defines the call, registration, and authorization boundaries.
A Function is one typed operation executed by the app server. Its request envelope contains:
method: the full name of the Function to callparams: untrusted caller inputcontext: caller, channel, language, authentication, and config data assembled by AppStore
A handler normally receives validated context and params and returns a result; failures return a structured error. Trust context only after verifying the request's x-signature.
In TypeScript, use @Func, @InputSchema, @OutputSchema, @Ctx, and @Input. The SDK publishes Zod schemas as JSON Schema and also validates input and output at runtime. In Go, appsdk.Register and appsdk.MustRegister derive schemas from Go structs and register typed handlers.
There are two kinds of Function:
Extension Function: belongs to a standard Extension. For example, command's
metadata.getCommandsis exposed asextension.command.metadata.getCommands.Standalone Function: app-specific business behavior, such as
tutorial.openororders.sync. In TypeScript, put@Funcon a provider without@Extension; in Go, useappsdk.Register.
AppStore discovers Function names and input/output schemas through extension.core.function.getFunctions. The SDK implements that discovery response, the PUT /functions/:version route, dispatch, and validation, so a new app does not need its own raw JSON-RPC router.
An Extension is a named, system-versioned capability contract that declares related Functions and metadata as a Channel feature. It is not merely a folder or class grouping. AppStore uses a registered contract to expose commands, open widgets or custom tabs, deliver hooks, and connect OAuth or config flows.
Common Extensions include command, widget, custom tab, hook, OAuth, config, calendar, polling, commerce, order, WMS, and messaging. Each has standard Function names and schemas. Start with the Extension guide, then check the TypeScript Extension reference and the typed helper for your language before implementing one.
The current recommended TypeScript path is:
Use @Extension only for an official Extension name supported by the SDK. Do not invent an Extension just to hold app-specific Functions; use standalone @Func methods instead. A decorated class must also be listed in the NestJS module's providers for discovery.
In Go, prefer typed builders such as extension/command, extension/oauth, and extension/config. Register a Function without a standard helper through appsdk.Register, and use the generic builder only when you actually need a separate Extension contract.
Auto-registration uses an app token to call registerExtension(appId, extensionName, systemVersion). AppStore then calls the discovery Function at the Function Endpoint to read metadata and schemas. Registration success does not prove that handlers work, so test discovery and real Function calls separately.
An input schema constrains untrusted params; an output schema checks that the app returns the AppStore contract. For a standard Extension, reuse schemas exported by the SDK. Recreating a same-named DTO makes contract changes easy to miss.
Depending on the call surface, context can contain:
caller: theuser,manager,system, orappactorchannel: identity of the Channel where the app is installeduser,userChat, andlanguage: user context when that flow provides itauthToken: a provider access token decrypted and injected for an OAuth connectionconfig: values and credentials stored through the config Extension for the current scope
Do not assume optional fields are always present. Validate them for the Function's execution surface. ctx.authToken is an external OAuth provider token, not a Channel App app or channel token.
A WAM (Web App Module) is an app web UI opened inside a Channel client. When a command, widget, custom tab, or another Function returns an action result like the following, the client loads the {name} UI below the registered WAM Endpoint.
Serve the built SPA from ${WAM_ENDPOINT}/${name} and wrap the React root in WamProvider.
useWamData/useTypedWamData: readappId,channelId,managerId, chat context, andwamArgsuseCallFunction: call your own app Function through AppStoreuseNativeFunction: call a Channel native function allowed for the current surface and manager/user authorizationuseWamSize,useWamClose: control WAM size and closing
Read the WAM guide for the complete React setup, runtime data, Function call, resize, and close flow.
A WAM must not store or issue the App Secret, Signing Key, app token, or channel token. wamArgs is client-readable too, so never place secrets, access tokens, or raw customer data in it. For work performed as the app or bot, call the app server with useCallFunction and let the server use a channel token. Use useNativeFunction only for work performed by the current manager or user.
Keep these credentials distinct:
Value | Meaning | Storage |
|---|---|---|
App ID | Public app identifier | May be used by the server and WAM |
App Secret | Long-lived secret used to issue app/channel token pairs | Server secret manager only |
Signing Key | Verifies | Server secret manager only |
App token | Extension registration and app-scoped native operations | Server cache |
Channel token | Server-side channel-scoped operations in an installed Channel | Server cache, separated by channel |
Manager/User authorization | Native operations performed by the current Channel client user | Managed by the WAM host runtime |
Provider OAuth token | Calls to a connected external service | Injected into |
Config credential | API keys, | Stored by AppStore and injected through |
Incoming Function authentication and outgoing native authentication are separate:
Incoming request: verify the HMAC-SHA256
x-signatureover the exact raw request body with the hex-encoded Signing Key. UseSignatureGuardwith NestJSrawBody: truein TypeScript, orserver.WithSignaturein Go. Never disable verification in production.Outgoing server request:
TokenManageruses the App Secret to issue and cache an app or channel token. It refreshes before expiry and deduplicates concurrent issue/refresh work. Do not callissueTokenfor every request.Outgoing WAM request: the WAM SDK calls its host bridge. The Channel runtime decides manager/user authorization; the app server's
TokenManagerdoes not mint it.
Use TokenManager instead of repeatedly calling the low-level issuance APIs. Use the following
contract when diagnosing the transport or intentionally managing tokens yourself.
issueTokenandrefreshTokenshare a limit of 10 calls per 30 minutes per app. Cache the access/refresh token pair instead of issuing a token for every request.Omitting
channelIdfromissueTokencreates an app token for app-scoped operations such as Extension registration.Supplying the
channelIdof an installed Channel creates a channel token for server-side operations in that Channel. The operation still requires installation and the selected permission.The important result fields are
accessToken,refreshToken, andexpiresInin seconds. Send the current access token in thex-access-tokenheader for Native Function requests.Channel permissions restrict operations performed by the server with a channel token. Manager/User permissions and authorization are enforced by the WAM host from the current user and surface.
Read the TypeScript authentication and token reference and Go authentication and token reference for exact APIs and custom cache storage.
The default token cache is in memory and suitable for one process. For multiple replicas, implement the SDK cache interface with shared storage such as Redis or a database so replicas share token pairs. In-flight deduplication is process-local; add storage-side locking if strict cross-replica refresh coordination is required. Never log access tokens, refresh tokens, provider tokens, or credentials.
Use the OAuth Extension and ctx.authToken for an OAuth Authorization Code flow. client_credentials, API keys, and per-shop credentials are not user-redirect OAuth; store them through the Config Extension and use them server-side.
A native Function is the opposite call direction from an app Function: an app server or WAM asks Channel to perform a capability through AppStore.
Server: TypeScript
NativeFunctionClientor Gonative.ClientToken lifecycle: TypeScript or Go
TokenManagerWAM:
useNativeFunction
Do not assume every language has the same typed wrapper. Check Go feature parity. When a wrapper is missing, isolate the protocol call in a small transport adapter and test its method and request/response contract.
Register endpoint roots in the developer portal, without a system version or WAM name:
Function Endpoint:
https://app.example.com/functionsActual Function call:
PUT https://app.example.com/functions/v1WAM Endpoint:
https://app.example.com/resource/wamActual WAM UI:
https://app.example.com/resource/wam/tutorial
proto/ is the shared wire-contract source for TypeScript and Go. App developers normally use each language SDK's decorators, builders, schemas, and types instead of generated proto code. When documentation or an example disagrees with a public export, follow the public export and schema implementation.
Continue with Function registration, the Command guide, the WAM guide, the Extension guide, and finally the production readiness guide. For runnable code, see the TypeScript tutorial and Go tutorial.