I Pointed an AI Coding Agent at the Webhook Docs and It Built a Working Auto-Reply Bot — UnifyPort
You know the move. There’s a messaging integration on your todo list, and instead of reading the docs end to end you open Cursor — or Claude Code, or Windsurf — and start typing. In 2026 that’s how most of us start a project. The real question isn’t whether you’ll vibe-code it. It’s whether the API you’re building against gives the agent enough to actually finish the job cleanly.
This is a build log. By the end you’ll have a running auto-reply bot: a webhook receiver that verifies the signature on every inbound message and replies through a single send endpoint — about an afternoon of work, most of it the agent’s. The reason it goes fast is that UnifyPort’s webhook is normalized. There’s one event shape to learn and one endpoint to call back, so the agent has nothing to guess.
Setup: point the agent at the docs
The single most important step is the one people skip: get the API reference into the agent’s context before you ask for code. Three approaches that work today:
- Paste the page. Copy the
message.receivedevent shape and thePOST /v1/messagessend section straight into the chat. - Attach the docs. Cursor’s
@Docs, Claude Code’s file context, Windsurf’s docs panel — point it at the UnifyPort reference so the agent can read it on demand. - Use a docs/MCP connector if your tool supports one, so the agent pulls the reference itself.
The payoff is concrete. An agent that has actually read the event names won’t invent an onMessage() handler or a sendText() SDK that doesn’t exist. It writes against message.received and POST /v1/messages, because that’s what’s in front of it. Garbage context in, hallucinated endpoints out — so spend the two minutes to get the real ones in.
The build, prompt by prompt
First prompt — the receiver:
Using the UnifyPort webhook docs I gave you, write an Express server with a
POST /webhookroute that receives standard events. For now, wheneventismessage.received, logfromandtext.
Because the event shape is fixed, the handler is just a branch on one field:
import express from "express";
const app = express();
app.use(express.json());
app.post("/webhook", (req, res) => {
const evt = req.body;
if (evt.event === "message.received") {
console.log(`${evt.provider} ${evt.from}: ${evt.text}`);
}
res.sendStatus(200);
});
app.listen(3000, () => console.log("listening on :3000"));
Second prompt — verify the signature. This is the step a careless build skips and a docs-aware agent doesn’t:
Each delivery is signed with HMAC-SHA256 using the webhook’s
signing_secret. Verify it before trusting the body. Use the raw request body and a timing-safe comparison.
The one thing the agent has to get right here: the HMAC must run over the raw bytes, not the re-serialized JSON. Prompted with “use the raw request body,” a good agent wires up express.json’s verify hook to capture rawBody. If it forgets, that’s your single review catch — and you’ll catch it the first time a real delivery returns 401.
Third prompt — reply through the send endpoint:
When a
message.receivedarrives, reply to the sender viaPOST /v1/messagesusing theaccount_idandfromfrom the event. Auth is a Bearer API key from the environment.
That’s the whole bot. Here’s the complete file the agent lands on:
import express from "express";
import crypto from "crypto";
const { UNIFYPORT_API_KEY, UNIFYPORT_SIGNING_SECRET } = process.env;
const app = express();
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } }));
function isValid(req) {
const sig = req.get("x-unifyport-signature") || "";
const expected = crypto
.createHmac("sha256", UNIFYPORT_SIGNING_SECRET)
.update(req.rawBody)
.digest("hex");
return sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
app.post("/webhook", async (req, res) => {
if (!isValid(req)) return res.sendStatus(401);
const evt = req.body;
if (evt.event === "message.received") {
await fetch("https://api.unifyport.ai/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${UNIFYPORT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
account_id: evt.account_id,
to: evt.from,
text: "Thanks! We got your message — a human will follow up shortly.",
}),
});
}
res.sendStatus(200);
});
app.listen(3000, () => console.log("listening on :3000"));
Run it and watch a message arrive
Connect one account (Telegram is the quickest to test), register a webhook endpoint pointed at your server with a signing_secret and subscribed_events: ["message.received"], then send your bot a DM. The event lands looking like this:
{
"event": "message.received",
"account_id": "acct_8Q2vK",
"provider": "telegram",
"from": "user_3f9c1a",
"text": "Hey, are you open on weekends?",
"timestamp": 1749427200,
"message_id": "tg_msg_5d2b7e"
}
Your server verifies the signature, calls POST /v1/messages, and the reply appears back in the chat. If the signature check fails, you’ll get a 401 — that’s the rawBody bug from above. Fix it once and the loop is solid.
Extend it
Here’s where the normalized webhook pays off. Adding a second platform isn’t a second integration — it’s zero new handler code. Connect a WhatsApp, LINE, or Zalo account, subscribe the same webhook, and the exact same message.received branch runs, because the event shape doesn’t change between providers. The provider field tells you where it came from; the rest of your code never looks.
Want the bot to actually answer instead of just acknowledging? That’s one more prompt: “before replying, send text to an LLM and use its response as the reply body.” The agent already wrote the send call — you’re swapping one string for a model response.
The lesson of the afternoon isn’t “AI writes your bot for you.” It’s that an AI agent is only as good as the surface you point it at. A normalized webhook with stable event names and one send endpoint is exactly the kind of surface an agent finishes cleanly — one shape to learn, nothing to invent. Point your tool at the UnifyPort v1 API, paste in the message.received shape, and you’ll have a verified, replying bot before the afternoon’s out — then turn it loose on the other five channels.