Verno developer API v1 — reference
Base: https://verno.co.in. All server calls: Authorization: Bearer vk_test_... or vk_live_.... Errors are {"detail": "<plain message>"} with the HTTP status below. Every key has scopes and a per-minute limit (429 + Retry-After when over it). A revoked key fails at once (401). A switched-off app's keys get 403.
| Method + path | Scope | Body / query | Answer |
|---|---|---|---|
GET /v1/me | any | — | {app_id, mode, scopes} |
GET /v1/users/{user_id} | users.read | — | {user_id, scopes, name?, photo_url?} (name/photo only with profile) |
POST /v1/bot/messages | bot.send | {user_id, text} (≤1000) | {message_id, delivery}; 403 if the person never wrote in your app chat, said Stop or removed your app; 429 over 20 a day to one person |
GET /v1/bot/threads | bot.read | limit | {threads: [{user_id, started_chat, following, stopped, removed, last_message_at}]} |
GET /v1/bot/threads/{user_id}/messages | bot.read | limit | {messages: [{id, direction, body, created_at}]} |
POST /v1/business-messages | messages.send | {user_id, template_id, params[]} or {user_id, text} | {message_id, shown_from, delivery}; 403: not verified / not approved / template not approved / outside the 24 h window for text / person doesn't have, follow or chat with the app / stopped / paused by Verno; 400: wording or a link not allowed |
POST /v1/business-messages/broadcast | messages.broadcast | {template_id, params[]} | {id, sent_to, skipped, queued}; followers only; 1 a day; big lists are sent in batches (queued: true) |
POST /v1/payments | payments.record | {amount: "450.50", kind: upi_handoff | marked_paid, reference?, user_id?} | {id, amount_paise, status: "recorded_not_verified"} |
POST /v1/relations | relations.write | {user_id, kind: bill | booking, reference?} | {id, status}: pending until the person confirms it in Vernolink; only then may you send them a first template |
POST /v1/events | events.write | {name: page_view | widget_tap | booking | order | form_reply | error | slow_page, target?, user_id?} | {recorded} |
POST /v1/webhooks/test | webhooks.test | — | {delivery_id} (a ping event) |
POST /v1/oauth/token | none (PKCE) | form: grant_type=authorization_code, client_id, code, code_verifier, redirect_uri | {access_token, token_type, expires_in: 3600, scope, user_id}; 400 invalid_grant |
GET /v1/oauth/userinfo | person token vt_... | — | {user_id, scopes, name?, photo_url?} |
Payments are records the business makes: customers pay the business directly by UPI. Verno never touches the money, and the dashboard labels them "recorded, not verified".
Webhook events
Common envelope: {"id": "whd_...", "event": "...", "mode": "test|live", "created": "<ISO time>", "data": {...}}.
| Event | data |
|---|---|
message.received | {user_id, message_id, text, first} — a person (or test user) wrote in your app chat |
chat.opted_out / chat.opted_in | {user_id} — Stop / Start in your app chat |
app.installed | {user_id, scopes} |
app.uninstalled | {user_id} — remove their access; tokens already stopped |
user.delete_data | {user_id, requested_at} — delete everything you hold about this person (DPDP) |
ping | {hello} |
Headers: Verno-Event, Verno-Delivery (same on retries), Verno-Timestamp (Unix seconds), Verno-Signature: v1=HEX(HMAC-SHA256(secret, "<timestamp>.<raw body>")). Refuse timestamps older than 5 minutes.
Key scopes
users.read, bot.send, bot.read, messages.send, messages.broadcast, payments.record, events.write, webhooks.test. Person scopes (consent screen): profile, chat, location.
Limits
Per key: 1-600 requests a minute (default 60); every request counts, also the ones that fail. Bot replies: 20 per person per day. Broadcasts: 1 a day. Business message templates: 30 per app, up to 3 placeholders and 3 buttons (each opens a page of your own app).