Deploying a Hosted Agent
So you chose the Hosted listing type — buyers subscribe and your agent runs for them automatically. This guide is for sellers who have a working agent (a script, a bot, an automation) but aren't sure how to "deploy" it or connect it to AgentDukaan. No prior DevOps experience assumed.
Prefer to let an AI write the integration? Hand the copy-paste prompts in Build & Verify Your Agent with AI to Claude/Cursor/Copilot with your code — they add (or audit) everything this guide describes. This guide is the hand-written version of the same thing.
Don't want to run a server at all? That's fine — Hosted isn't your only option. Sell your work as Source code, Done-for-you, or a Build-it guide instead (see the Seller Guide). Come back here only if you want buyers to use your agent without setting anything up themselves.
1. What "hosted" actually means
A hosted agent has two halves:
-
Your agent — the code that does the work (sends WhatsApp messages, scrapes leads, calls an LLM, whatever). It runs on a computer that is always on and reachable from the internet over HTTPS. That computer is your "server" — you don't own hardware; you rent it from a cloud platform for a few hundred rupees a month.
-
A webhook — a single HTTPS URL on your server that AgentDukaan calls to say "a buyer just subscribed." Your agent then pulls that buyer's credentials from AgentDukaan over a signed request, sets itself up, and starts running. Secrets (API keys, tokens) are never pushed to you — you fetch them when you need them and hold them in memory.
Buyer subscribes & fills setup wizard
│
▼
AgentDukaan ──POST buyer_configured (signed)──▶ https://your-server.com/hooks/agentdukaan
▲ │
│ ◀──────── GET /config (pull the keys, signed) ───┤
│ ▼
└──────────── POST /run (report usage) ──── your agent runs for that buyer
You provide that webhook URL in Step 3 of the listing creator ("Webhook endpoint"). It must be HTTPS in production.
2. The integration contract
Three messages to understand: get notified, pull the credentials, and report runs.
2a. Inbound — buyer_configured (AgentDukaan → you)
When a buyer finishes setup, AgentDukaan sends an HTTP POST to your webhook endpoint. This is a notification — it does not contain the buyer's secrets.
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
X-AgentDukaan-Event | buyer_configured |
X-AgentDukaan-Sig | HMAC-SHA256 of the raw request body, hex, signed with your listing's webhook secret |
Body
{
"event": "buyer_configured",
"subscriptionId": "665f…", // identifies this buyer's subscription — save it
"buyerId": "664a…",
"listingId": "663b…",
"tier": { "name": "Growth", "priceINR": 3999, "runLimitPerMonth": 6000, "features": [ … ] },
"config": { "business_name": "Mehta Jewellers", "language": "hinglish" }, // NON-sensitive only
"sensitiveFields": ["waba_token"], // ids you must fetch (step 2b)
"credentialsUrl": "https://api.agentdukaan.in/api/subscriptions/665f…/config",
"ts": 1718600000000
}
You must respond with HTTP 2xx. If you don't (or you time out — the limit is 10s), AgentDukaan retries up to 3 times with exponential backoff, and the buyer's dashboard shows the setup as pending / failed instead of delivered.
On receipt: fetch the credentials (2b), cache them in memory against subscriptionId, and provision your agent. You'll get another buyer_configured whenever the buyer rotates or fixes a key — re-fetch then.
2b. Pull credentials — GET /api/subscriptions/{subscriptionId}/config (you → AgentDukaan)
The secrets (API keys, tokens — the fields you marked sensitive) are never pushed. Fetch them from the credentialsUrl, authenticated with the same HMAC as /run (the 5-minute timestamp window makes each request a short-lived token).
Headers
| Header | Value |
|---|---|
X-AgentDukaan-Ts | current time in milliseconds (within 5 min of server clock) |
X-AgentDukaan-Sig | HMAC-SHA256 of `${subscriptionId}.${ts}`, hex, signed with your webhook secret |
Response
{ "success": true, "data": { "config": { "business_name": "Mehta Jewellers", "waba_token": "EAAG…", "language": "hinglish" } } }
data.config is the decrypted config including the sensitive values — use it directly. Hold it in memory only; do not write it to your database or logs. Fetch on buyer_configured, cache for the process lifetime, and re-fetch on the next event. Every fetch is recorded in AgentDukaan's audit log.
For the authoritative field-by-field contract (including the reconfigure flow), see Hosted Agent — Webhook & Callback Contract.
2c. Outbound — report a run (you → AgentDukaan)
Each time your agent does a billable run for a buyer, tell AgentDukaan so run-limit enforcement and analytics work:
POST {API_URL}/api/subscriptions/{subscriptionId}/run
Headers
| Header | Value |
|---|---|
X-AgentDukaan-Ts | current time in milliseconds (must be within 5 minutes of the server's clock) |
X-AgentDukaan-Sig | HMAC-SHA256 of the string `${subscriptionId}.${ts}`, hex, signed with your webhook secret |
Response
{ "success": true, "data": { "allowed": true, "remaining": 5994 } }
A 429 RUN_LIMIT_EXCEEDED means the buyer hit their tier's monthly run cap — stop running until the next period.
Your webhook secret
Signing (above) uses a per-listing webhook secret that the platform provisions. Use the "Test webhook" button on your listing to confirm your endpoint is reachable and your signature check works. If you don't have a secret value to verify against yet, request it from the AgentDukaan team — it isn't currently exposed as an editable field in the listing UI.
3. Deploy your agent (step by step)
If you've never deployed anything, the easiest path is a managed platform that takes your code from GitHub and gives you a public HTTPS URL automatically. Railway and Render both have free/cheap tiers and need no server administration.
The example below is a minimal Node.js webhook receiver. The same idea works in Python, etc. — the platform doesn't care what language you use, only that you expose one HTTPS POST route.
Step 1 — Put your agent in a Git repo
Create a GitHub repository with your agent code. Add a tiny web server that exposes the webhook route.
// server.js — minimal hosted-agent webhook receiver (Node + Express)
import express from 'express'
import crypto from 'crypto'
const app = express()
// IMPORTANT: capture the RAW body so the signature matches byte-for-byte.
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf } }))
const SECRET = process.env.AGENTDUKAAN_WEBHOOK_SECRET // your per-listing secret
const API_URL = process.env.AGENTDUKAAN_API_URL || 'https://api.agentdukaan.in'
app.post('/hooks/agentdukaan', (req, res) => {
// 1) Verify the signature
const expected = crypto.createHmac('sha256', SECRET).update(req.rawBody).digest('hex')
const got = req.header('X-AgentDukaan-Sig') || ''
if (got.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
return res.status(401).send('bad signature')
}
// 2) The body has NO secrets — just ACK fast, then pull the config in the background
const { event, subscriptionId } = req.body
if (event === 'buyer_configured') {
fetchConfig(subscriptionId)
.then((config) => configCache.set(subscriptionId, config)) // in memory only — don't persist secrets
.catch((e) => console.error('config fetch failed', subscriptionId, e))
}
// 3) ACK fast (do heavy work in the background)
res.status(200).json({ ok: true })
})
app.listen(process.env.PORT || 3000, () => console.log('agent up'))
// agentdukaan.js — sign once, fetch credentials + report runs
import crypto from 'crypto'
const SECRET = process.env.AGENTDUKAAN_WEBHOOK_SECRET
const API_URL = process.env.AGENTDUKAAN_API_URL || 'https://api.agentdukaan.in'
export const configCache = new Map() // subscriptionId → decrypted config (memory only)
function sign(subscriptionId) {
const ts = String(Date.now())
const sig = crypto.createHmac('sha256', SECRET).update(`${subscriptionId}.${ts}`).digest('hex')
return { 'X-AgentDukaan-Ts': ts, 'X-AgentDukaan-Sig': sig }
}
// Pull the buyer's real keys when you need them. Hold in memory; re-fetch on the
// next buyer_configured event. Never write these to your DB or logs.
export async function fetchConfig(subscriptionId) {
const res = await fetch(`${API_URL}/api/subscriptions/${subscriptionId}/config`, {
headers: sign(subscriptionId),
})
if (!res.ok) throw new Error(`config fetch ${res.status}`)
return (await res.json()).data.config
}
// Call this when your agent does a billable run.
export async function reportRun(subscriptionId) {
const res = await fetch(`${API_URL}/api/subscriptions/${subscriptionId}/run`, {
method: 'POST',
headers: sign(subscriptionId),
})
if (res.status === 429) return { allowed: false } // monthly limit reached
return (await res.json()).data
}
Step 2 — Deploy it
Railway (similar on Render):
- Sign in at railway.app with GitHub.
- New Project → Deploy from GitHub repo → pick your repo.
- Under Variables, add
AGENTDUKAAN_WEBHOOK_SECRET,AGENTDUKAAN_API_URL, and any of your own (LLM keys, DB URL, …). Never hard-code secrets in your code. - Railway builds and runs it, then gives you a public URL like
https://your-agent-production.up.railway.app. Make sure it serves on the port fromprocess.env.PORT.
Step 3 — Wire it into your listing
- Your webhook URL is the public URL + your route, e.g.
https://your-agent-production.up.railway.app/hooks/agentdukaan. - Paste it into Webhook endpoint in the listing creator (Step 3).
- Click Test webhook — you should see a success response. Fix signature/URL issues until it's green.
Step 4 — Keep it running
- Managed platforms restart your app on crash. Persist non-sensitive subscription state (which buyers are active, their settings) in a database so a restart doesn't lose them — but don't persist the fetched secrets; re-pull them with
fetchConfigafter a restart (or lazily on first use) so keys live only in memory. - Add basic logging so you can see incoming webhooks and run reports.
- Watch your platform's free-tier limits; upgrade before you hit them so the agent doesn't sleep.
4. Go live
- Build your setup wizard so buyers can hand you the config your agent needs — see Seller Guide §5. Mark API keys/tokens
sensitive. - Upload a demo video.
- Submit for QA (requires approved KYC). Once an admin approves it, your agent is live and discoverable.
5. Troubleshooting
| Symptom | Likely cause |
|---|---|
| Test webhook fails / times out | URL wrong, app not running, or not listening on process.env.PORT. Check your platform's logs. |
401 bad signature in your logs | You hashed the parsed JSON instead of the raw body, or the secret doesn't match the platform's. |
Setup shows failed/pending on the buyer's dashboard | Your endpoint didn't return 2xx within 10s. ACK immediately, do heavy work after responding. |
run returns 401 | Timestamp older than 5 min (clock drift) or signature computed over the wrong string — it must be `${subscriptionId}.${ts}`. |
run returns 429 | Buyer hit their tier's monthly run limit; resume next period. |
| The webhook body has no API keys | Expected — secrets are never pushed. Fetch them from credentialsUrl / GET …/config (see §2b above). The webhook only carries non-sensitive settings + sensitiveFields. |
GET …/config returns 401 | Same causes as /run 401: clock drift >5 min, or signature computed over the wrong string (`${subscriptionId}.${ts}`). |
See also
- Seller Guide — listing types, pricing, the full create → QA → live flow.
- Build & Verify Your Agent with AI — let an AI assistant add or audit this integration.
- Webhook & Callback Contract — the field-by-field reference.