Put your Vercel agent on more channels
Keep your agent on Vercel. Keiki brings it SMS, iMessage, WhatsApp, Slack, Telegram and more.
Your agent keeps running on Vercel, on your model and your code. Keiki receives every channel message, sends it to your agent as a signed turn, and delivers the reply. Your app never holds channel credentials or a Keiki dashboard token.
Expose your agent to more channels
Set the Keiki agent's harness to external in push mode, with dispatchUrl pointing at your deployment's turn route
(see External harness):
{
"harness": "external",
"harness_config": {
"mode": "push",
"dispatchUrl": "https://your-app.vercel.app/api/keiki"
}
}The app needs no secret. Keiki signs every turn with its Ed25519 platform key, and createKeikiHandler
verifies it against Keiki's published keys. To use a per-agent secret instead, generate one with an API
key for that agent and store it in the Vercel project as KEIKI_SIGNING_SECRET:
curl -X POST https://onkeiki.com/api/v1/developer/agents/<agent-id>/harness/signing-secret \
-H "Authorization: Bearer sk_live_…"Install @onkeiki/ai-sdk, @vercel/functions, ai and @ai-sdk/mcp, then add the turn route. waitUntil lets
the function acknowledge the dispatch with 202 and keep running the model:
// app/api/keiki/route.ts
import { createKeikiHandler, keikiDelivered } from '@onkeiki/ai-sdk'
import { waitUntil } from '@vercel/functions'
import { stepCountIs, streamText } from 'ai'
export const maxDuration = 300
export const POST = createKeikiHandler({
waitUntil,
onTurn: async ({ instructions, messages, tools, prepareStep, abortSignal }) =>
streamText({
model: 'openai/gpt-5.4-nano',
instructions,
messages,
tools: await tools(),
prepareStep,
stopWhen: [keikiDelivered, stepCountIs(10)],
abortSignal,
}),
})stopWhen lets the model read tool results before it answers. keikiDelivered stops the turn once reply or
send_message has delivered, so the answer is not sent twice; a failed send returns an error and the model can try again.
The handler verifies Keiki's Ed25519 signature against the raw body and authenticates its turn API and MCP calls
with the request's per-turn Keiki-Turn-Token. With KEIKI_SIGNING_SECRET set, it verifies the HMAC
Keiki-Signature and signs those calls with the secret instead. It reports the reply and completion back to Keiki. Keiki keeps the turn trace, history and delivery. tools()
returns the turn's Keiki tools (channel, capability and skill tools), authorized for that agent and turn only; the
app never gets a workspace token. See AI SDK integration
for arrivals, cancellation and serving several agents.
To develop locally, run the app and point the agent at it through the tunnel:
curl -fsSL https://onkeiki.com/api/v1/harness/tunnel.sh \
| KEIKI_API_KEY=sk_live_… PORT=3000 TURN_PATH=/api/keiki shexamples/vercel-connect-next in the repository is this route as a runnable Next.js app.
Add channels
Attach channels to the Keiki agent in the dashboard under Channels: an iMessage or SMS line, Slack, WhatsApp, Telegram or an email inbox. Messages on any of them reach the same turn route; nothing changes in your app.
Set it up from your app
An app can do the setup itself with one OAuth grant for one agent. In Vercel Connect, create a connector from
https://onkeiki.com/api/v1/agent-channels; Connect discovers Keiki's channels issuer
(https://onkeiki.com/.well-known/oauth-authorization-server/channels) and registers itself, and every client
registered there can only be granted channels. Any other OAuth client can do the same with dynamic client
registration and PKCE. The user picks an agent, or names a new one, on the consent screen; the token reaches that
agent's channel setup and nothing else in the workspace. An org needs a plan to grant it.
With that token, point the agent at the deployment and attach channels:
# The bound agent, its dispatch settings and its channels.
curl https://onkeiki.com/api/v1/agent-channels -H "Authorization: Bearer $TOKEN"
curl -X PUT https://onkeiki.com/api/v1/agent-channels/dispatch \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"mode":"push","dispatchUrl":"https://your-app.vercel.app/api/keiki"}'
curl -X POST https://onkeiki.com/api/v1/agent-channels/channels \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"channels":[{"type":"telegram","bot_token":"…"}]}'channels takes the same channel specs as POST /api/v1/developer/deployments. A channel that needs a browser
step (Slack, WhatsApp) comes back with a link to finish it. If the agent is deleted, the token stops working with
401 and the app has to ask the user to connect again.