Commands & interactions
Interactions
The lifecycle of a command, button, menu or form — and every way to answer it.
An interaction is created when a member uses one of your commands (slash or context menu), clicks a button, picks from a select menu or submits a form. Voznest validates it first — command options, the member's permissions, the component's existence — so your agent only receives well-formed requests. They are still untrusted user input: validate values before acting on them.
The payload
json
{ "id": "66fb…", // interaction id "token": "…", // answer with it (valid 15 min) — never log it "type": "command", // "command" | "component" | "modal_submit" "applicationId": "…", "serverId": "…", // the hub "channelId": "…", // where it happened (a channel or a thread) "user": { "id": "…", "username": "ana", "displayName": "Ana", "avatarUrl": "…" }, "member": { "roleIds": ["…"], "nickname": null, "permissions": ["viewChannels", "sendMessages", …] }, "data": { // command: { commandId, name, options: [{ name, type, value }], resolved: { users, channels, roles } } // context menu: { commandId, name, kind: "user" | "message", targetId, target: { user } | { message } } // component: { customId, componentType: "button" | "select", values?: ["…"] } // modal_submit: { customId, fields: [{ customId, value }] } }, "message": { … } // component interactions: the message the control is on}Answering
http
POST https://api.voznest.com/api/v1/interactions/{id}/{token}/callbackContent-Type: application/json{ "type": "reply", "data": { "content": "Done!", "ephemeral": true } }| type | When | Effect |
|---|---|---|
reply | First answer or follow-up | Posts a message (content, embeds, components). ephemeral: true shows it only to the member and isn't stored. |
defer | First answer only | Acknowledges; the member sees that your app is working. Reply within 15 minutes. |
update | Component interactions | Edits the message the button/menu belongs to instead of posting a new one. |
modal | First answer only | Opens a form; its submission arrives as a new modal_submit interaction. See Modals. |
Timing
- 5 seconds to send the first answer (
reply,defer,updateormodal). Otherwise the member is told your app didn't respond and the token stops accepting a first answer. - 15 minutes after the interaction, the token expires (
INTERACTION_EXPIRED). Follow-up replies are accepted until then. - If no agent of the right environment is connected, the member immediately gets
APP_OFFLINE— your code never sees the request.
ts
// Slow work: acknowledge now, answer later (same endpoint, within 15 minutes).await answer(d, { type: "defer" }); // member sees "… is working on it"const report = await buildReport(); // takes 30 secondsawait answer(d, { type: "reply", data: { content: report } });await answer(d, { type: "reply", data: { content: "Also: …" } }); // follow-ups are allowedts
// A button that updates its own message (no new message).if (d.type === "component" && d.data.customId === "count:+1") { const n = Number(d.message.content.match(/\d+/)?.[0] ?? 0) + 1; await answer(d, { type: "update", data: { content: `Count: ${n}` } });}Which agent answers
The environment installed in the hub decides: Production hubs → the primary (production) agent; test hubs → that environment's agent. The callback endpoint itself needs no Authorization header — the token is the credential, so keep it out of logs.
The member's effective permissions in that channel are in
member.permissions. Commands with requiredPermission are already filtered, but buttons are clickable by anyone who can see the message — check permissions for privileged buttons.