@andreprado/agentkit 0.1.0-alpha.20 → 0.1.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -200,7 +200,7 @@ export type ChannelAdapter = ChannelAdapterId & {
200
200
  };
201
201
 
202
202
  export type PortableChannelRouteContract = {
203
- route: "/channels/:name/:type/:provider/webhook";
203
+ route: "/channels/:name/:type/:provider/webhook" | "/channels/:name/webhook";
204
204
  verification: WebhookVerificationResult;
205
205
  dedupe: {
206
206
  key: string;
@@ -30,8 +30,8 @@ const AGENT_RUNTIMES = new Set<AgentRuntime>(["local", "edge"]);
30
30
  const PROVIDERS = new Set<AgentProviderName>(["test", "openai", "anthropic", "openrouter", "custom"]);
31
31
  const ACCESS_MODES = new Set<AccessMode>(["private", "token", "public"]);
32
32
  const TOOL_VISIBILITIES = new Set<ToolVisibility>(["user", "internal"]);
33
- const CHANNEL_TYPES = new Set<ChannelType>(["website", "telegram", "whatsapp", "discord", "slack"]);
34
- const CHANNEL_PROVIDERS = new Set<ChannelProvider>(["agentkit", "telegram", "zapster", "meta", "discord", "slack"]);
33
+ const CHANNEL_TYPES = new Set<ChannelType>(["website", "telegram", "whatsapp", "discord", "slack", "webhook"]);
34
+ const CHANNEL_PROVIDERS = new Set<ChannelProvider>(["agentkit", "telegram", "zapster", "meta", "uazapi", "evolution", "discord", "slack", "generic"]);
35
35
  const CHANNEL_BUFFER_MODES = new Set<ChannelBufferMode>(["off", "debounce"]);
36
36
  const CHANNEL_AUDIO_MODES = new Set<ChannelAudioMode>(["off", "reject", "transcribe"]);
37
37
  const DISCORD_CHANNEL_MODES = new Set<DiscordChannelMode>(["interactions", "bot"]);
@@ -627,6 +627,11 @@ function validateChannel(value: unknown, index: number, hasTranscriptionConfig:
627
627
  return;
628
628
  }
629
629
 
630
+ if (value.type === "webhook") {
631
+ validateGenericWebhookChannel(value, index);
632
+ return;
633
+ }
634
+
630
635
  validateChannelAudio(value.audio, index, hasTranscriptionConfig);
631
636
 
632
637
  if (value.type === "website") {
@@ -699,7 +704,7 @@ function validateTelegramChannel(value: Record<string, unknown>, index: number):
699
704
 
700
705
  function validateWhatsappChannel(value: Record<string, unknown>, index: number): void {
701
706
  if (value.type !== "whatsapp") {
702
- throw invalidConfig(`channels[${index}].type must be "website", "telegram", "whatsapp", "discord", or "slack".`);
707
+ throw invalidConfig(`channels[${index}].type must be "website", "telegram", "whatsapp", "discord", "slack", or "webhook".`);
703
708
  }
704
709
 
705
710
  if (value.provider === "zapster") {
@@ -720,7 +725,25 @@ function validateWhatsappChannel(value: Record<string, unknown>, index: number):
720
725
  return;
721
726
  }
722
727
 
723
- throw invalidConfig(`channels[${index}].provider must be "zapster" or "meta" when type is "whatsapp".`);
728
+ if (value.provider === "uazapi") {
729
+ expectRequiredSecrets(
730
+ value.secrets,
731
+ ["UAZAPI_BASE_URL", "UAZAPI_TOKEN"],
732
+ `channels[${index}]`,
733
+ );
734
+ return;
735
+ }
736
+
737
+ if (value.provider === "evolution") {
738
+ expectRequiredSecrets(
739
+ value.secrets,
740
+ ["EVOLUTION_API_BASE_URL", "EVOLUTION_API_KEY", "EVOLUTION_INSTANCE_NAME", "EVOLUTION_WEBHOOK_TOKEN"],
741
+ `channels[${index}]`,
742
+ );
743
+ return;
744
+ }
745
+
746
+ throw invalidConfig(`channels[${index}].provider must be "zapster", "meta", "uazapi", or "evolution" when type is "whatsapp".`);
724
747
  }
725
748
 
726
749
  function validateDiscordChannel(value: Record<string, unknown>, index: number): void {
@@ -755,6 +778,18 @@ function validateSlackChannel(value: Record<string, unknown>, index: number): vo
755
778
  expectRequiredSecrets(value.secrets, ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"], `channels[${index}]`);
756
779
  }
757
780
 
781
+ function validateGenericWebhookChannel(value: Record<string, unknown>, index: number): void {
782
+ if (value.provider !== "generic") {
783
+ throw invalidConfig(`channels[${index}].provider must be "generic" when type is "webhook".`);
784
+ }
785
+
786
+ if (value.audio !== undefined) {
787
+ throw invalidConfig(`channels[${index}].audio is not supported when type is "webhook".`);
788
+ }
789
+
790
+ expectRequiredSecrets(value.secrets, ["AGENTKIT_WEBHOOK_SECRET"], `channels[${index}]`);
791
+ }
792
+
758
793
  function validateChannelLimits(value: unknown, index: number): void {
759
794
  if (value === undefined) {
760
795
  return;
@@ -6,7 +6,7 @@ export type RuntimeTargetId = (typeof runtimeTargetIds)[number];
6
6
  export type RuntimeTargetCapabilityState = "available" | "planned" | "unsupported";
7
7
  export type RuntimeStorageCapability = "agentkit" | "durable-object-sqlite" | "local-sqlite" | "postgres";
8
8
  export type RuntimeFilesCapability = "none" | "r2" | "local-disk" | "s3-compatible";
9
- export type RuntimeChannelCapability = "website" | "telegram" | "whatsapp" | "discord" | "slack";
9
+ export type RuntimeChannelCapability = "website" | "telegram" | "whatsapp" | "discord" | "slack" | "webhook";
10
10
  export type RuntimeProviderSupport = "pi-local" | "edge-compatible" | "server-env";
11
11
 
12
12
  export type RuntimeTargetCapabilities = {
@@ -35,7 +35,7 @@ export const runtimeTargets: Readonly<Record<RuntimeTargetId, RuntimeTarget>> =
35
35
  dev: "available",
36
36
  storage: ["local-sqlite"],
37
37
  files: ["local-disk"],
38
- channels: ["website", "slack"],
38
+ channels: ["website", "slack", "webhook"],
39
39
  providers: ["pi-local"],
40
40
  },
41
41
  },
@@ -48,7 +48,7 @@ export const runtimeTargets: Readonly<Record<RuntimeTargetId, RuntimeTarget>> =
48
48
  dev: "planned",
49
49
  storage: ["agentkit", "durable-object-sqlite", "postgres"],
50
50
  files: ["none", "r2"],
51
- channels: ["website", "telegram", "whatsapp", "discord", "slack"],
51
+ channels: ["website", "telegram", "whatsapp", "discord", "slack", "webhook"],
52
52
  providers: ["edge-compatible"],
53
53
  },
54
54
  },
@@ -61,7 +61,7 @@ export const runtimeTargets: Readonly<Record<RuntimeTargetId, RuntimeTarget>> =
61
61
  dev: "planned",
62
62
  storage: ["local-sqlite", "postgres"],
63
63
  files: ["none", "local-disk", "s3-compatible"],
64
- channels: ["website", "telegram", "whatsapp", "discord", "slack"],
64
+ channels: ["website", "telegram", "whatsapp", "discord", "slack", "webhook"],
65
65
  providers: ["server-env"],
66
66
  },
67
67
  },
@@ -74,7 +74,7 @@ export const runtimeTargets: Readonly<Record<RuntimeTargetId, RuntimeTarget>> =
74
74
  dev: "unsupported",
75
75
  storage: ["local-sqlite", "postgres"],
76
76
  files: ["none", "local-disk", "s3-compatible"],
77
- channels: ["website", "telegram", "whatsapp", "discord", "slack"],
77
+ channels: ["website", "telegram", "whatsapp", "discord", "slack", "webhook"],
78
78
  providers: ["server-env"],
79
79
  },
80
80
  },
@@ -9,10 +9,13 @@ import { openCapsuleStore, type ConversationRecord } from "../storage/sqlite";
9
9
  import type { AgentChannel, ChannelProvider, ChannelType } from "../index";
10
10
  import type { ChannelAdapter, NormalizedChannelMessage, RawWebhookEvent } from "./channels";
11
11
  import { discordChannelAdapter } from "./channels/discord";
12
+ import { genericWebhookChannelAdapter } from "./channels/generic-webhook";
12
13
  import { slackChannelAdapter } from "./channels/slack";
13
14
  import { telegramChannelAdapter } from "./channels/telegram";
14
15
  import { websiteChannelAdapter } from "./channels/website";
16
+ import { evolutionWhatsappChannelAdapter } from "./channels/whatsapp-evolution";
15
17
  import { metaWhatsappChannelAdapter } from "./channels/whatsapp-meta";
18
+ import { uazapiWhatsappChannelAdapter } from "./channels/whatsapp-uazapi";
16
19
  import { zapsterWhatsappChannelAdapter } from "./channels/whatsapp-zapster";
17
20
  import { runAgentMessage } from "./chat";
18
21
  import { isChannelBufferEnabled, renderBufferedChannelMessage, resolveChannelBuffer } from "./channel-buffer";
@@ -320,13 +323,14 @@ async function handleDevServerRequest(
320
323
  return await handleLocalFile(capsule, request, decodeURIComponent(fileMatch[1]));
321
324
  }
322
325
 
323
- const channelMatch = route.match(/^\/channels\/([^/]+)\/(website|telegram|whatsapp|discord|slack)\/([^/]+)\/webhook$/);
326
+ const channelMatch = route.match(/^\/channels\/([^/]+)\/(website|telegram|whatsapp|discord|slack|webhook)\/([^/]+)\/webhook$/);
327
+ const directWebhookMatch = route.match(/^\/channels\/([^/]+)\/webhook$/);
324
328
 
325
- if (channelMatch) {
329
+ if (channelMatch || directWebhookMatch) {
326
330
  return await handlePortableChannel(capsule, request, channelRuntime, {
327
- name: decodeURIComponent(channelMatch[1]),
328
- type: channelMatch[2] as ChannelType,
329
- provider: decodeURIComponent(channelMatch[3]) as ChannelProvider,
331
+ name: decodeURIComponent((channelMatch ?? directWebhookMatch)?.[1] ?? ""),
332
+ type: channelMatch ? channelMatch[2] as ChannelType : "webhook",
333
+ provider: channelMatch ? decodeURIComponent(channelMatch[3]) as ChannelProvider : "generic",
330
334
  });
331
335
  }
332
336
 
@@ -1137,6 +1141,14 @@ function adapterForChannel(type: ChannelType, provider: ChannelProvider): Channe
1137
1141
  return metaWhatsappChannelAdapter;
1138
1142
  }
1139
1143
 
1144
+ if (type === "whatsapp" && provider === "uazapi") {
1145
+ return uazapiWhatsappChannelAdapter;
1146
+ }
1147
+
1148
+ if (type === "whatsapp" && provider === "evolution") {
1149
+ return evolutionWhatsappChannelAdapter;
1150
+ }
1151
+
1140
1152
  if (type === "discord" && provider === "discord") {
1141
1153
  return discordChannelAdapter;
1142
1154
  }
@@ -1145,6 +1157,10 @@ function adapterForChannel(type: ChannelType, provider: ChannelProvider): Channe
1145
1157
  return slackChannelAdapter;
1146
1158
  }
1147
1159
 
1160
+ if (type === "webhook" && provider === "generic") {
1161
+ return genericWebhookChannelAdapter;
1162
+ }
1163
+
1148
1164
  throw new AgentKitError("channel_not_found", `No channel adapter for ${type}/${provider}.`);
1149
1165
  }
1150
1166
 
@@ -462,11 +462,30 @@ export function slackChannel(input) {
462
462
  };
463
463
  }
464
464
 
465
+ export function webhookChannel(input) {
466
+ return {
467
+ type: "webhook",
468
+ provider: "generic",
469
+ name: input.name,
470
+ secrets: normalizeChannelSecrets(input.secrets ?? ["AGENTKIT_WEBHOOK_SECRET"], "generic webhook channel"),
471
+ ...(input.limits ? { limits: input.limits } : {}),
472
+ ...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "generic webhook channel") } : {}),
473
+ };
474
+ }
475
+
465
476
  function defaultWhatsappChannelSecrets(provider) {
466
477
  if (provider === "zapster") {
467
478
  return ["ZAPSTER_API_KEY", "ZAPSTER_INSTANCE_ID", "ZAPSTER_WEBHOOK_ID"];
468
479
  }
469
480
 
481
+ if (provider === "uazapi") {
482
+ return ["UAZAPI_BASE_URL", "UAZAPI_TOKEN"];
483
+ }
484
+
485
+ if (provider === "evolution") {
486
+ return ["EVOLUTION_API_BASE_URL", "EVOLUTION_API_KEY", "EVOLUTION_INSTANCE_NAME", "EVOLUTION_WEBHOOK_TOKEN"];
487
+ }
488
+
470
489
  return ["META_WHATSAPP_ACCESS_TOKEN", "META_WHATSAPP_APP_SECRET", "META_WHATSAPP_VERIFY_TOKEN"];
471
490
  }
472
491
 
@@ -1128,9 +1147,10 @@ export default {
1128
1147
 
1129
1148
  function isHostedChannelWebhookPath(pathname) {
1130
1149
  return (
1131
- /^\\/channels\\/[^/]+\\/(?:website\\/agentkit|telegram\\/telegram|whatsapp\\/(?:zapster|meta)|discord\\/discord|slack\\/slack)\\/webhook$/.test(pathname) ||
1132
- /^\\/channels\\/[^/]+\\/(?:website|telegram|discord|slack)\\/webhook$/.test(pathname) ||
1133
- /^\\/channels\\/[^/]+\\/whatsapp\\/(?:zapster|meta)\\/webhook$/.test(pathname)
1150
+ /^\\/channels\\/[^/]+\\/(?:website\\/agentkit|telegram\\/telegram|whatsapp\\/(?:zapster|meta|uazapi|evolution)|discord\\/discord|slack\\/slack|webhook\\/generic)\\/webhook$/.test(pathname) ||
1151
+ /^\\/channels\\/[^/]+\\/(?:website|telegram|discord|slack|webhook)\\/webhook$/.test(pathname) ||
1152
+ /^\\/channels\\/[^/]+\\/webhook$/.test(pathname) ||
1153
+ /^\\/channels\\/[^/]+\\/whatsapp\\/(?:zapster|meta|uazapi|evolution)\\/webhook$/.test(pathname)
1134
1154
  );
1135
1155
  }
1136
1156
 
@@ -27,7 +27,7 @@ Use this first inside an AgentKit Agent Capsule.
27
27
  - Improve from hosted or local production evidence: `skills/agentkit-improve/SKILL.md`
28
28
  - Prepare hosted deploy: `skills/agentkit-deploy/SKILL.md`
29
29
  - Work with secrets, external APIs, public access, channels, or real data: `skills/agentkit-security/SKILL.md`
30
- - Add or debug website, Telegram, WhatsApp, Discord, or Slack channels: `skills/agentkit-channels/SKILL.md`
30
+ - Add or debug website, Telegram, WhatsApp, Discord, Slack, or generic webhook channels: `skills/agentkit-channels/SKILL.md`
31
31
  - Investigate command failures: `skills/agentkit-troubleshooting/SKILL.md`
32
32
 
33
33
  For a compact docs map, read `references/docs-router.md`.
@@ -9,7 +9,7 @@ Prefer the narrowest source that covers the task.
9
9
  - Safe AgentKit product feedback drafts/submission: `docs/guides/send-feedback.md`
10
10
  - Real provider setup: `docs/guides/use-provider.md`
11
11
  - Deploy readiness, managed secrets, smoke checks, hosted UI: `docs/guides/prepare-deploy.md`
12
- - Channels: `docs/guides/add-channel.md`, `connect-discord.md`, `connect-slack.md`, `connect-telegram.md`, `connect-whatsapp-zapster.md`, `debug-channel.md`
12
+ - Channels: `docs/guides/add-channel.md`, `connect-discord.md`, `connect-slack.md`, `connect-telegram.md`, `connect-whatsapp-evolution.md`, `connect-whatsapp-uazapi.md`, `connect-whatsapp-zapster.md`, `debug-channel.md`
13
13
  - Security: `docs/guides/security-rules.md`
14
14
 
15
15
  Use `npm run agentkit -- docs full` only for a complete-contract audit, framework internals, or a behavior not covered by the task guide.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: agentkit-channels
3
- description: Use when adding, connecting, testing, buffering, transcribing audio, or debugging AgentKit website, Telegram, WhatsApp, Discord, or Slack channels, including channel config helpers, provider secrets, webhook setup, channel tests, delivery logs, burst-message buffers, and transcription provider secrets.
3
+ description: Use when adding, connecting, testing, buffering, transcribing audio, or debugging AgentKit website, Telegram, WhatsApp, Discord, Slack, or generic webhook channels, including channel config helpers, provider secrets, webhook setup, channel tests, delivery logs, burst-message buffers, and transcription provider secrets.
4
4
  ---
5
5
 
6
6
  # AgentKit Channels
@@ -47,7 +47,7 @@ V1 providers:
47
47
  - `openai`: `gpt-4o-mini-transcribe`, `gpt-4o-transcribe`, `whisper-1`; default secret `OPENAI_API_KEY`.
48
48
  - `groq`: `whisper-large-v3-turbo`, `whisper-large-v3`, `distil-whisper-large-v3-en`; default secret `GROQ_API_KEY`.
49
49
 
50
- Telegram voice notes are usually OGG/Opus, so use Groq for the default Telegram voice-note path in V1. Zapster audio needs a usable HTTPS Zapster media download URL in the webhook payload; arbitrary hosts are rejected before bearer auth is sent. Hosted channel creation requires the transcription secret automatically when the channel enables transcription. Webhooks only enqueue audio jobs; download and transcription run in the retryable channel worker before the agent run.
50
+ Telegram voice notes are usually OGG/Opus, so use Groq for the default Telegram voice-note path in V1. Zapster audio needs a usable HTTPS Zapster media download URL in the webhook payload; arbitrary hosts are rejected before bearer auth is sent. UAZAPI audio downloads go through UAZAPI `/message/download` with base64 return enabled. Evolution audio downloads go through Evolution API `/chat/getBase64FromMediaMessage/{instance}` with base64 return enabled. Arbitrary UAZAPI and Evolution webhook media hosts are not fetched. Hosted channel creation requires the transcription secret automatically when the channel enables transcription. Webhooks only enqueue audio jobs; download and transcription run in the retryable channel worker before the agent run.
51
51
 
52
52
  Discord channels do not support audio in V1.
53
53
 
@@ -80,9 +80,12 @@ npm run agentkit -- channels list
80
80
  npm run agentkit -- channels add website website-chat
81
81
  npm run agentkit -- channels connect telegram support-telegram
82
82
  npm run agentkit -- channels add whatsapp support-whatsapp --provider zapster
83
+ npm run agentkit -- channels connect whatsapp support-whatsapp --provider uazapi
84
+ npm run agentkit -- channels connect whatsapp main-whatsapp --provider evolution
83
85
  npm run agentkit -- channels connect discord support-discord
84
86
  npm run agentkit -- channels connect discord server-discord --mode bot
85
87
  npm run agentkit -- channels connect slack support-slack
88
+ npm run agentkit -- channels connect webhook n8n-webhook
86
89
  npm run agentkit -- channels doctor support-telegram
87
90
  npm run agentkit -- channels test support-telegram --message "hello"
88
91
  npm run agentkit -- channels test-audio support-telegram --fixture voice-note
@@ -95,6 +98,8 @@ npm run agentkit -- channels deliveries list support-telegram
95
98
  - `references/discord.md`
96
99
  - `references/slack.md`
97
100
  - `references/telegram.md`
101
+ - `references/whatsapp-evolution.md`
102
+ - `references/whatsapp-uazapi.md`
98
103
  - `references/whatsapp-zapster.md`
99
104
  - `references/channel-buffering.md`
100
105
  - `references/channel-debugging.md`
@@ -102,3 +107,21 @@ npm run agentkit -- channels deliveries list support-telegram
102
107
  ## Safety
103
108
 
104
109
  Do not paste provider tokens into code, docs, fixtures, prompts, evals, or delivery logs. Webhook URLs are public transport endpoints; authenticity comes from provider validation or channel tokens.
110
+
111
+ ## Generic Webhooks
112
+
113
+ Use `webhookChannel({ name: "n8n-webhook" })` for n8n, Make, Zapier, Pipedream, or custom server events.
114
+
115
+ Canonical JSON:
116
+
117
+ ```json
118
+ {
119
+ "event_id": "evt_123",
120
+ "external_id": "customer_123",
121
+ "message": "hello from n8n"
122
+ }
123
+ ```
124
+
125
+ `event_id` is required for dedupe. `external_id` is recommended for conversation continuity and is hashed before storage. Authenticate with `Authorization: Bearer <AGENTKIT_WEBHOOK_SECRET>`, `X-AgentKit-Webhook-Secret`, or `X-AgentKit-Webhook-Signature: sha256=<hmac of raw JSON body>`.
126
+
127
+ Generic webhooks are inbound-only in V1. Add a separate tool if the agent must call back into n8n after it runs.
@@ -62,5 +62,5 @@ Common errors:
62
62
  - `channel_limit_exceeded`: backpressure skipped the message.
63
63
  - `synthetic_expected_failure`: a synthetic test reached AgentKit, but the provider correctly rejected a fake test recipient.
64
64
  - `buffered` delivery state: message is waiting for the channel quiet window or max wait before one coalesced agent run is queued.
65
- - `adapter_stubbed`: explicit dry-run mode built a provider request but did not call the provider.
65
+ - `adapter_stubbed`: adapter completed without calling an outbound provider, either because explicit dry-run mode is enabled or the channel is inbound-only.
66
66
  - `provider_sent`: the provider accepted the outbound send and returned a provider message ID.
@@ -0,0 +1,57 @@
1
+ # WhatsApp Through Evolution API
2
+
3
+ Required secrets:
4
+
5
+ ```txt
6
+ EVOLUTION_API_BASE_URL
7
+ EVOLUTION_API_KEY
8
+ EVOLUTION_INSTANCE_NAME
9
+ EVOLUTION_WEBHOOK_TOKEN
10
+ ```
11
+
12
+ Audio transcription also needs the configured transcription secret, usually `OPENAI_API_KEY` or `GROQ_API_KEY`.
13
+
14
+ Commands:
15
+
16
+ ```sh
17
+ agentkit deploy
18
+ agentkit channels add whatsapp main-whatsapp --provider evolution
19
+ agentkit channels setup main-whatsapp --apply
20
+ agentkit channels status main-whatsapp
21
+ agentkit channels test main-whatsapp --message "hello"
22
+ agentkit channels deliveries list main-whatsapp
23
+ ```
24
+
25
+ AgentKit applies Evolution setup by calling `/webhook/set/{instance}` with the stable hosted URL and then confirming the configured webhook through `/webhook/find/{instance}`. AgentKit appends `?token=<EVOLUTION_WEBHOOK_TOKEN>` to the registered webhook URL.
26
+
27
+ Inbound Evolution `MESSAGES_UPSERT` webhooks normalize text and audio messages. `fromMe` messages are skipped to avoid reply loops. Unsupported media should be logged as skipped/unsupported without creating an agent run.
28
+
29
+ Outbound replies call Evolution API `POST /message/sendText/{instance}` with the `apikey` header and a JSON body containing `number` and `text`. Only set `AGENTKIT_CHANNEL_SEND_DRY_RUN=1` in tests when Evolution should not receive a real message.
30
+
31
+ Buffer rapid WhatsApp messages:
32
+
33
+ ```ts
34
+ whatsappChannel({
35
+ name: "main-whatsapp",
36
+ provider: "evolution",
37
+ buffer: {
38
+ mode: "debounce",
39
+ quietWindowMs: 2500,
40
+ maxWaitMs: 12000,
41
+ maxMessages: 20,
42
+ maxChars: 8000,
43
+ },
44
+ })
45
+ ```
46
+
47
+ Transcribe WhatsApp audio:
48
+
49
+ ```ts
50
+ whatsappChannel({
51
+ name: "main-whatsapp",
52
+ provider: "evolution",
53
+ audio: { mode: "transcribe" },
54
+ })
55
+ ```
56
+
57
+ Evolution audio downloads use `/chat/getBase64FromMediaMessage/{instance}` and AgentKit rejects unsafe Evolution base URLs before sending `EVOLUTION_API_KEY`; do not use localhost, private-network hosts, or HTTP base URLs.
@@ -0,0 +1,61 @@
1
+ # WhatsApp Through UAZAPI
2
+
3
+ Required secrets:
4
+
5
+ ```txt
6
+ UAZAPI_BASE_URL
7
+ UAZAPI_TOKEN
8
+ ```
9
+
10
+ Optional hardening secret:
11
+
12
+ ```txt
13
+ UAZAPI_WEBHOOK_TOKEN
14
+ ```
15
+
16
+ Audio transcription also needs the configured transcription secret, usually `OPENAI_API_KEY` or `GROQ_API_KEY`.
17
+
18
+ Commands:
19
+
20
+ ```sh
21
+ agentkit deploy
22
+ agentkit channels add whatsapp support-whatsapp --provider uazapi
23
+ agentkit channels setup support-whatsapp --apply
24
+ agentkit channels status support-whatsapp
25
+ agentkit channels test support-whatsapp --message "hello"
26
+ agentkit channels deliveries list support-whatsapp
27
+ ```
28
+
29
+ AgentKit applies UAZAPI setup by calling `/webhook` with the stable hosted URL and then confirming that UAZAPI lists the configured webhook. If the channel declares `UAZAPI_WEBHOOK_TOKEN`, AgentKit appends `?token=<UAZAPI_WEBHOOK_TOKEN>` to the registered webhook URL.
30
+
31
+ Inbound UAZAPI `messages` webhooks normalize text and audio messages. `fromMe` and `wasSentByApi` messages are skipped to avoid reply loops. Unsupported media should be logged as skipped/unsupported without creating an agent run.
32
+
33
+ Outbound replies call UAZAPI `POST /send/text` with the `token` header and a JSON body containing `number`, `text`, `readchat`, `async`, `track_source`, and `track_id`. Only set `AGENTKIT_CHANNEL_SEND_DRY_RUN=1` in tests when UAZAPI should not receive a real message.
34
+
35
+ Buffer rapid WhatsApp messages:
36
+
37
+ ```ts
38
+ whatsappChannel({
39
+ name: "support-whatsapp",
40
+ provider: "uazapi",
41
+ buffer: {
42
+ mode: "debounce",
43
+ quietWindowMs: 2500,
44
+ maxWaitMs: 12000,
45
+ maxMessages: 20,
46
+ maxChars: 8000,
47
+ },
48
+ })
49
+ ```
50
+
51
+ Transcribe WhatsApp audio:
52
+
53
+ ```ts
54
+ whatsappChannel({
55
+ name: "support-whatsapp",
56
+ provider: "uazapi",
57
+ audio: { mode: "transcribe" },
58
+ })
59
+ ```
60
+
61
+ UAZAPI audio downloads use `/message/download` with `return_base64: true`, `return_link: false`, `generate_mp3: false`, and `transcribe: false`. AgentKit rejects unsafe UAZAPI base URLs before sending `UAZAPI_TOKEN`; do not use localhost, private-network hosts, or HTTP base URLs.