Skip to content

Receive events

Markdown

Amedeus sends your app an HTTPS POST when something happens that it asked to hear about. You choose the events and the request URL under Manage → Installed apps → the app → Event subscriptions.

  1. Verify the signature of every request (below).
  2. Answer 2xx within 3 seconds. Do the work afterwards, for example from a queue. Anything else is a failed delivery.
  3. Be idempotent. An event can arrive more than once (retries). event_id is the same each time.
Event When Needs
message.channel A person posts in a channel the app belongs to channels:history
message.im A person sends the app a direct message channels:history
app_mention A person mentions the app, in a channel or a direct message app_mentions:read

Messages written by apps or AI agents never reach apps, so two apps can’t trigger each other.

One message can arrive as more than one event. A message that mentions your app in a channel is delivered as message.channel and as app_mention; a direct message that mentions it, as message.im and app_mention. A retry also repeats an event. Subscribe only to what you need, and answer each event.message_id once, as the templates do.

A direct message with an app exists only while the app accepts messages: App Home → Allow people to message this app (off by default). Receiving message.im and accepting messages are separate switches.

{
"type": "event_callback",
"event_id": "f3e62f45-273b-489e-9aed-c2dca1437849",
"event_time": 1794511135,
"workspace_id": "80f1d0c5-9e89-4657-88e0-754997a08bfa",
"app_id": "2362dcb0-4f20-4fc7-b1a6-9cd06923679d",
"bot_user_id": "e83f53c0-f56b-42a5-89e4-f18b5c9a31f4",
"event": {
"type": "message.im",
"message_id": "e2a4be8f-eaa6-4e21-b1da-8b022c30441d",
"channel": "890d8292-33b6-4d9a-9f05-772c8571137e",
"channel_kind": "dm",
"user": "7118b1bc-22c0-4bdd-98bd-2014b89b0e66",
"text": "probando el sobre del evento",
"parent_id": null,
"mentions": [],
"created_at": "2026-11-12T19:18:55.014928+00:00"
}
}
Field Meaning
type event_callback, or url_verification while you save the URL
event_id Unique per event. The same on every retry: use it to ignore duplicates
event_time When it was sent, in seconds since the epoch
workspace_id The workspace where it happened
app_id Your app
bot_user_id Your app’s own user. Compare it with mentions to know whether you were named
event.type One of the event types above
event.message_id The message
event.channel The conversation
event.channel_kind public, private or dm
event.user Who wrote it
event.text Markdown, as written
event.parent_id The thread the message replies to, or null
event.mentions Ids of the people mentioned
event.created_at When the message was written

To answer, call the Web API with your token, for example POST /messages with channel set to event.channel and parent_id set to reply in a thread.

Every request carries these headers:

Header Value
X-Amedeus-Signature v0= + hex HMAC-SHA256 of v0:{timestamp}:{raw body}, keyed with the signing secret
X-Amedeus-Request-Timestamp Seconds since the epoch
X-Amedeus-Retry-Num 0 the first time, then 1, 2, 3
X-Amedeus-Retry-Reason On retries only: why the previous attempt failed (see below)
X-Amedeus-Event-Type The event type, or slash_command, or url_verification

The signing secret is under Event subscriptions → Signing secret. Compute the signature over the raw body (before parsing), compare in constant time, and reject timestamps older than 5 minutes.

import crypto from "node:crypto";
function verify(req, rawBody, signingSecret) {
const timestamp = req.headers["x-amedeus-request-timestamp"];
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected =
"v0=" +
crypto.createHmac("sha256", signingSecret).update(`v0:${timestamp}:${rawBody}`).digest("hex");
const given = String(req.headers["x-amedeus-signature"] ?? "");
return (
given.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))
);
}

When you save a URL, Amedeus first sends {"type":"url_verification","challenge":"…"}, signed like any other request. Answer 200 with the challenge as plain text, or as {"challenge":"…"}. The URL is not saved until it does. The page then shows when it last answered (“Verified”).

A slash command with its own URL is verified the same way when you save it, so it must answer the challenge too. A command without one uses the events URL, which was already verified.

A failed delivery is tried again after 1 minute, 5 minutes and 30 minutes: four attempts in all. Redirects are not followed, so give the final address (with https and any trailing slash). X-Amedeus-Retry-Reason says why the last attempt failed:

Reason Meaning
http_error Your URL answered a status other than 2xx
http_timeout No answer within 3 seconds
connection_failed The server couldn’t be reached
ssl_error The certificate couldn’t be verified
unknown_error Anything else

If almost every delivery fails (more than 95% of at least 20 in an hour), Amedeus switches the app’s events off and records it in the activity log. Saving the settings again turns them back on. Enable events pauses them on purpose, keeping the URL and your choices; slash commands are not affected.