Create a channel adapter
Normalize a message provider into verified inbound events and capability-aware outbound delivery.
A ChannelAdapter is the only message-transport contract the runtime requires. It verifies ingress, translates provider events, sends normalized output, and declares exactly what the provider supports.
const channel: ChannelAdapter = {
defaultPath: '/webhooks/acme',
capabilities: {
approvals: 'text',
cards: false,
edits: true,
files: true,
groups: true,
mediaGroups: false,
reactions: false,
readReceipts: false,
streaming: 'edit',
threads: true,
typing: false,
},
async receive(request) {
const body = await verifyAndParseAcmeWebhook(request)
return { response: new Response(null, { status: 202 }), events: normalize(body) }
},
async send(event, { signal } = {}) {
const result = await deliverToAcme(event, signal)
return { status: 'sent', externalId: result.id }
},
}Ingress rules
Verify the signature before trusting any body field. Bound body size. Reject stale signatures and use constant-time comparison. Preserve provider event IDs for deduplication. Populate installation, conversation, thread, actor, attachment, and timestamp fields from provider-stable identifiers.
Return challenge responses synchronously when the provider requires verification. Otherwise prefer a fast accepted response after durable enqueue.
Output rules
Implement only event kinds declared in capabilities. Reject unsupported kinds clearly. Respect the abort signal and provider retry hints. Split oversized messages without losing order, and use deterministic part IDs so retries do not duplicate only part of a response.
Attachments
resolveAttachment should accept only attachment references created by the verified inbound message, authenticate the provider download, enforce a byte limit, and return bytes with normalized metadata. Do not turn it into an arbitrary URL fetcher.
Lifecycle
Webhook adapters need only receive and send. Socket or long-polling providers can implement start(emit) and return a close function. health should verify required configuration and a lightweight provider operation.