Notifications
This guide covers delivery patterns after a query triggers, with Auto as the source of truth for event emission.
title and description in notificationsThe title and description you set on the query are included in outbound notifications across channels (Telegram, webhook, notify). Recipients often see an alert hours or days after the query was created — these fields are what make it immediately clear what fired and why it was set up. See Title and Description.
- Create your query with Create Query.
- Start delivery with
telegram_bot,webhook, or SSE stream. - Run Agent Runner so the agent can continue actions after each trigger.
Delivery Channels (Side-by-Side)
| Channel | Best for | Setup |
|---|---|---|
| Webhook | Production agent automation | action.type = "webhook" with signature verification and queue/worker processing |
| Telegram Bot | Fast human-readable alerts | action.type = "telegram_bot" with params.botToken + params.chatId for direct delivery, or webhook/SSE relay for custom formatting. The body is auto-composed from query title + description + trigger context — no message param. |
| SSE Stream | Real-time event consumers | Always available regardless of the selected action — GET /v2/auto/queries/{queryId}/stream, using the same auth as query creation. For minimal setup, add a notify action with a message; those notifications are retrievable only via SSE or poll. |
Event Payload Contract (Canonical)
To keep webhook, Telegram relay, and SSE processing consistent, normalize incoming events into one internal contract.
Canonical event object:
Note: SSE uses Server-Sent Events format with event: notification:new.
The canonical payload below is for documentation reference only — actual SSE delivery uses the format defined in the SSE section.
{
"id": 12345,
"type": "athena_query_notify_only",
"category": "alerts",
"title": "Query triggered: BTC > 100000",
"body": "BTC price crossed above 100000",
"data": {
"queryId": "a12d20ff-6cb2-433e-afed-cc2e6a0380b6"
},
"priority": "high",
"createdAt": "2026-04-01T12:00:00.000Z"
}
Webhook Event Sample
Headers:
X-Auto-Event-Id: 12345
X-Auto-Signature-Timestamp: 1775035200
X-Auto-Signature: v1=<hmac_hex>
Body example:
{
"id": 12345,
"type": "athena_query_notify_only",
"category": "alerts",
"title": "Query triggered: BTC > 100000",
"body": "BTC price crossed above 100000",
"data": {
"queryId": "a12d20ff-6cb2-433e-afed-cc2e6a0380b6"
},
"priority": "high",
"createdAt": "2026-04-01T12:00:00.000Z"
}
Telegram Relay Event Sample
If you use direct telegram_bot actions, delivery can go straight to Telegram.
If you use relay mode, normalize to a Telegram job object:
{
"id": 12345,
"queryId": "a12d20ff-6cb2-433e-afed-cc2e6a0380b6",
"channel": "telegram",
"chatId": "<CHAT_ID>",
"text": "BTC trigger fired: price > threshold",
"priority": "high"
}
SSE Event Sample
SSE frame:
id: 12345
event: notification:new
data: {"id":12345,"type":"athena_query_notify_only","category":"alerts","title":"Query triggered: BTC > 100000","body":"BTC price crossed above 100000","data":{"queryId":"a12d20ff-6cb2-433e-afed-cc2e6a0380b6"},"priority":"high","createdAt":"2026-04-01T12:00:00.000Z"}
Note: The id in the SSE frame header is the notification ID (numeric). The queryId is contained in the data field of the payload and is a UUID. Use data.queryId to correlate with poll results via /v2/auto/queries/{queryId}.
Implementation note:
- Preserve the original payload for audit/debug.
- Map to the canonical contract before queueing downstream work.
Best Practice: Run a Background Orchestrator
When a condition triggers, avoid handling business logic inline in the event ingress handler.
Recommended pattern:
- Receive Auto event.
- Verify + dedupe (
eventId). - Push a job to your runner queue.
- Let a worker decide what the agent should do next.
- Execute and log outcomes.
Why this is preferred:
- Keeps ingestion fast and reliable
- Prevents duplicate downstream actions
- Makes policy and retries easier to manage
- Scales from local dev to cloud workers
1) Webhook Delivery (Recommended)
Use action.type = "webhook" and verify Auto signatures on receipt.
Signature inputs:
signing_key = SHA256(your_secret)
expected = HMAC_SHA256(signing_key, timestamp + "." + eventId + "." + rawBody)
Node.js Verification Snippet
import crypto from "crypto";
export function verifyAutoWebhook(
secret: string,
rawBody: string,
signatureHeader: string,
timestamp: string,
eventId: string,
): boolean {
if (!signatureHeader?.startsWith("v1=")) return false;
const given = signatureHeader.slice(3);
const signingKey = crypto.createHash("sha256").update(secret).digest();
const payload = `${timestamp}.${eventId}.${rawBody}`;
const expected = crypto
.createHmac("sha256", signingKey)
.update(payload)
.digest("hex");
if (given.length !== expected.length) return false;
return crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}
Operational notes:
- Enforce replay window checks with
X-Auto-Signature-Timestamp. - Deduplicate by
X-Auto-Event-Id. - Return
2xxquickly, then process asynchronously.
2) Telegram Bot Delivery
Telegram can be used as a primary delivery channel from day one.
Recommended patterns:
- Direct: use
action.type = "telegram_bot"in your query, withparams: { botToken, chatId }. - Relay: receive webhook/SSE events, transform payloads, and send to Telegram Bot API.
Relay flow:
- Receive Auto events (webhook/SSE).
- Transform message payload.
- Send message to Telegram Bot API.
Get Telegram Bot Token
- Open
@BotFatherin Telegram. - Run
/newbot. - Save the bot token (treat as secret).
Get Chat ID
- Send any message to the bot (or in a group where bot is present).
- Call:
curl "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates"
- Read
message.chat.idfrom the response.
Send Message
curl -X POST "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/sendMessage" \
-H "Content-Type: application/json" \
-d '{
"chat_id": "<CHAT_ID>",
"text": "Auto trigger fired for BTC RSI"
}'
3) SSE Stream Delivery
Endpoint: GET /v2/auto/queries/{queryId}/stream
Auth: the stream requires the same auth used to create the query — send x-elfa-api-key: <YOUR_API_KEY> for API-key queries, or the x402 secret for x402 queries. It is not limited to x-elfa-api-key.
Quick test:
curl -N "https://api.elfa.ai/v2/auto/queries/<QUERY_ID>/stream" \
-H "x-elfa-api-key: <YOUR_API_KEY>"
SSE best practices:
- Run SSE consumers on server/worker (not browser-only clients) so you can set auth headers.
- Reconnect automatically with backoff.
- Persist last seen event IDs to avoid duplicate downstream actions.
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
400 / 401 when polling or streaming | Missing/invalid API key or auth headers | Send x-elfa-api-key; include HMAC headers where required. |
| Webhook signature mismatch | Signing wrong payload (not raw body) or wrong secret | Verify with timestamp + "." + id + "." + rawBody and SHA256(secret) key. |
| Duplicate downstream actions | No idempotency on event processing | Deduplicate by id before enqueue/execute. |
| Event received but agent does nothing | Ingress processes inline and times out or fails | ACK quickly, push to queue, process in worker. |
| SSE disconnect/reconnect loops | No retry/backoff or unstable consumer | Add reconnect backoff and heartbeat monitoring. |
| Missing triggers after some time | Query expired or was cancelled | Poll query status and check expiresIn, status, and last evaluations. |
| Signature timestamp rejected | Runner clock skew | Sync server clock (NTP) and enforce bounded replay window. |
Recommended Auto-First Setup by Stage
| Stage | Suggested Pattern |
|---|---|
| Prototype | Auto Telegram + local worker |
| Production | Auto webhook + queue + worker |
| Real-time operations | Auto SSE + worker service |
Local vs Cloud Deployment
| Environment | Suggested Setup |
|---|---|
| Local development | SSE consumer + single worker process |
| Cloud production | Webhook ingress + queue + worker autoscaling |
For full orchestration guidance, see Agent Runner. For concrete local/cloud blueprints, see Reference Implementations (Local and Cloud).