Watchdog API
Slot capacity, named recipients, alerts and signed Webhooks.
Last updated: Sep 30, 2026Use GET /api/x402/pricing and the subscription quote endpoint for current products, watch types, signals, defaults and discounts. Paid types consume their own product capacity; free types have per-wallet limits. Paused watches still count. Purchase with spend; create or change watches with write. Creating a watch is not a subscription purchase.
curl 'https://ftso.app/api/x402/subscriptions/quote?product=watchdog-slot&quantity=5&periods=12' \
-H 'authorization: Bearer fbx_READ_TOKEN'
curl -X POST 'https://ftso.app/api/x402/subscriptions' \
-H 'authorization: Bearer fbx_SPEND_TOKEN' \
-H 'content-type: application/json' \
-H 'Idempotency-Key: purchase_20260930_001' \
-d '{"product":"watchdog-slot","quantity":5,"periods":12,"autoRenew":true}'Auto-renew repeats the purchased quantity and duration at the current price. Enabling it requires spend; disabling it requires write. Existing terms acceptance may be required before a purchase. A one-time trial is available through POST /api/x402/subscriptions/trial, subject to eligibility.
curl -X POST 'https://ftso.app/api/x402/watches' \
-H 'authorization: Bearer fbx_WRITE_TOKEN' \
-H 'content-type: application/json' \
-H 'Idempotency-Key: watch_20260930_001' \
-d '{"type":"feed-move","label":"FLR swing",
"config":{"chainId":14,"symbol":"FLR","percent":2.5,"windowMinutes":60},
"signals":{"price-move":{"enabled":true}}}'
curl -X PATCH 'https://ftso.app/api/x402/watches/7' \
-H 'authorization: Bearer fbx_WRITE_TOKEN' -H 'content-type: application/json' \
-d '{"action":"pause"}'PATCH supports config, signals, routing, label, cooldownSeconds, messageTemplate, webhookUrl, rotateSecret and pause/resume actions. Config changes reset monitoring state. Signal overrides merge field by field; null resets an override. signalsMode: replace replaces overrides, while an empty signals object with the default merge does nothing. Secret rotation and initial webhook setup reveal a secret: store it securely. A same-key replay may recover it during the 24-hour idempotency retention.
Named recipients can be reused across watches. Input channels can be created by machine clients; linked channels follow their own verification and permission rules. Discord linking remains session-only. Pending recipients cannot receive alerts or tests. Legacy channel endpoints address the oldest recipient of their type; prefer recipient IDs for precise control. Webhook URLs are masked in reads: omitting a target preserves it, an intentional empty/null removal clears it. Do not write a display mask back as a new URL.
curl -X POST 'https://ftso.app/api/x402/recipients' \
-H 'authorization: Bearer fbx_WRITE_TOKEN' -H 'content-type: application/json' \
-d '{"name":"Ops","type":"webhook","target":{"url":"https://example.com/hooks/ftsobest"}}'
# Use the returned recipient id in routing.
curl -X PATCH 'https://ftso.app/api/x402/watches/7' \
-H 'authorization: Bearer fbx_WRITE_TOKEN' -H 'content-type: application/json' \
-d '{"routing":{"default":["inapp",12],"groups":{"alerts":[12]}}}'A signal group route overrides the watch default; default recipients are the fallback. Account/system events always reach the bell and explicitly default recipients. Signal-alarm bell delivery can be disabled. Quiet hours hold personal channels; groups, Webhooks and the bell keep delivering. A watch cooldown suppresses repetitions of the same event, not a different event or its recovery.
curl -X PUT 'https://ftso.app/api/x402/settings' \
-H 'authorization: Bearer fbx_WRITE_TOKEN' -H 'content-type: application/json' \
-d '{"quietHours":{"start":"22:00","end":"07:00","timezone":"Europe/Berlin"},"notificationLocale":"de"}'
curl -X POST 'https://ftso.app/api/x402/message-preview' \
-H 'authorization: Bearer fbx_READ_TOKEN' -H 'content-type: application/json' \
-d '{"watchId":7,"notificationLocale":"de","scope":"wallet","messageTemplate":null}'Templates contain title, body and appendDetails. Watch templates override wallet templates; null restores the default. Placeholders are literal substitutions, never executable markup. Allowed placeholders: {watch}, {type}, {title}, {severity}, {emoji}, {network}, {identity}, {round}, {epoch}, {details}, {time}, {link}. Preview does not deliver a message.
curl 'https://ftso.app/api/x402/alerts?page=0&pageSize=20&watchId=7&severity=warning&delivery=failed&sort=time&direction=desc' \
-H 'authorization: Bearer fbx_READ_TOKEN'
curl 'https://ftso.app/api/x402/alerts/export?watchId=7' \
-H 'authorization: Bearer fbx_READ_TOKEN'History uses zero-based pages with pageSize at most 100. Export uses the same filters and returns at most 5,000 CSV rows. Watch and account lists use limit/cursor instead. Foreign watch or alert IDs return 404. Deleting a watch does not erase its alert history. Deleting alerts is an explicit write operation; selection and maxId protect the intended history window.
Payload v2 adds payloadVersion: 2, signal and group to the v1 shape. Keep technical fields unchanged even when rendered text is localized. Read the v2 schema and accept additional event data fields. Deduplicate by deliveryId. The generated event list below covers catalogued signal and recovery events; account messages and channel tests are separate system events.
This shortened v2 example shows the envelope; data contains type-specific facts. The v1 schema uses the same core delivery, alert, watch, event, severity, firedAt and data fields without the three v2 additions. rendered and renderedMessage can provide localized display text; keep deliveryId for deduplication.
{
"payloadVersion": 2,
"deliveryId": "unique-delivery-id",
"alertId": 41,
"watchId": 7,
"label": "FLR swing",
"type": "feed-move",
"event": "feed-move.threshold",
"signal": "price-move",
"group": "alerts",
"severity": "warning",
"firedAt": "2026-09-30T12:00:00.000Z",
"data": {}
}import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody is the exact request body Buffer, not parsed/re-serialized JSON.
const timestamp = req.headers["x-watchdog-timestamp"];
const signature = req.headers["x-watchdog-signature"];
if (
typeof timestamp !== "string" ||
!/^\d+$/.test(timestamp) ||
typeof signature !== "string" ||
Math.abs(Date.now() / 1000 - Number(timestamp)) > 300
)
throw new Error("invalid");
const expected = Buffer.from(
"sha256=" +
createHmac("sha256", secret)
.update(timestamp + ".")
.update(rawBody)
.digest("hex"),
);
const received = Buffer.from(signature);
if (received.length !== expected.length || !timingSafeEqual(expected, received))
throw new Error("invalid");Verification uses X-Watchdog-Signature and X-Watchdog-Timestamp; inspect X-Watchdog-Payload-Version for the payload contract. Respond only after validating the signature and freshness. Acknowledge duplicate delivery IDs without repeating effects. See Watchdog user documentation for the broader workflow.
- credits.low-balance
- credits.recovered
- feed-move.threshold
- fsp.balance-low
- fsp.balance-low.recovered
- fsp.fast-updates
- fsp.fast-updates.recovered
- fsp.fdc-bitvote
- fsp.fdc-bitvote-length
- fsp.fdc-bitvote-length.recovered
- fsp.fdc-bitvote.recovered
- fsp.fdc-consensus
- fsp.fdc-consensus.recovered
- fsp.fdc-reveal-offence
- fsp.fdc-reveal-offence.recovered
- fsp.fdc-signature
- fsp.fdc-signature.recovered
- fsp.fdc-submit1
- fsp.fdc-submit1.recovered
- fsp.ftso-commit
- fsp.ftso-commit.recovered
- fsp.ftso-reveal
- fsp.ftso-reveal.recovered
- fsp.ftso-signature
- fsp.ftso-signature.recovered
- fsp.mc-anchor
- fsp.mc-anchor.recovered
- fsp.mc-fdc
- fsp.mc-fdc.recovered
- fsp.mc-uptime
- fsp.mc-uptime.recovered
- fsp.monitoring
- fsp.monitoring.recovered
- fsp.not-in-policy
- fsp.not-in-policy.recovered
- fsp.registration
- fsp.registration.recovered
- fsp.reveal-offence
- fsp.reveal-offence.recovered
- fsp.rewards-unclaimed
- fsp.rewards-unclaimed.recovered
- fsp.signature-grace
- fsp.signature-grace.recovered
- fsp.signature-mismatch
- fsp.signature-mismatch.recovered
- fsp.tx-failed
- fsp.tx-failed.recovered
- fsp.validator-not-listed
- fsp.validator-not-listed.recovered
- provider.eligibility-lost
- provider.recovered
- provider.unregistered
- signing.done
- signing.missed
- signing.rewards-open
- signing.rewards-reminder
- signing.uptime-open
- wallet-balance.moved