Webhook Integrations
Send Tickory alerts to your own services, queues, bots, or automation tools. This guide covers setup, payload formats, signing headers, retries, and safe local testing.
Availability
Setup
- Open a scan and go to Notifications.
- Paste the destination URL into Webhook URL.
- Choose a payload preset:
- Default keeps the legacy Tickory payload.
- OpenClaw preset sends the versioned contract envelope.
- Add custom headers if your receiver expects bearer tokens or API keys.
- Save the alert config, then use Test All or the webhook test action to verify delivery.
Custom headers are for outbound auth
Authorization or X-Api-Key when your receiver requires authentication. Tickory masks saved header values when it reads the config back, so secrets are not shown in plain text after save.URL Requirements
- Only
http://andhttps://endpoints are accepted. - The hostname must resolve publicly.
localhost, loopback addresses, and private-network IP ranges are rejected. - If you add webhook headers or choose a payload preset, a webhook URL is required.
- Tickory currently accepts up to 20 custom webhook headers.
Reserved headers are set by Tickory and cannot be overridden from the UI:
Content-TypeUser-AgentHostIdempotency-Key- Any header beginning with
X-Tickory-
Header names must contain only letters, digits, and hyphens. Header values must be non-empty strings without control characters.
Payload Formats
Tickory sends one of two payload contracts: the legacy Default format or the versioned OpenClaw envelope. Both support individual alerts and aggregated events.
Default Payload: Individual Alert
The legacy individual alert payload uses payload_version: "1.0" and sends symbol-level indicator data.
{
"payload_version": "1.0",
"symbol": "BTCUSDT",
"exchange": "binance",
"price": 84500.12,
"rsi_14": 28.3,
"volume_quote": 1450000000,
"timestamp": "2026-03-16T11:45:00Z",
"metrics": {
"rsi_14": 28.3,
"close": 84500.12,
"volume_quote": 1450000000
},
"chart_url": "https://www.binance.com/en/futures/BTCUSDT",
"expression": "has_rsi_14 && rsi_14 < 30",
"timeframe": "1m"
}| Field | Type | Notes |
|---|---|---|
payload_version | string | Always 1.0 for legacy single-symbol alerts. |
symbol | string | Matched market symbol such as BTCUSDT. |
exchange | string | Current exchange identifier, typically binance. |
price, rsi_14, volume_quote | number | Primary market data included for the matched symbol. |
timestamp | string | RFC 3339 timestamp for the matched candle. |
metrics | object | Metric map of matched values. |
chart_url, expression, timeframe | string | Scan context used to render the alert. |
Default Payload: Summary / Batch Events
Tickory batches high-volume runs. When a scan finds 4 or more matches, it emits a single summary event instead of one alert per symbol.
{
"payload_version": "1.1",
"event_type": "summary",
"scan_name": "Momentum Reversal",
"scan_run_id": "run_01hsw3d6a2n9xq",
"timestamp": "2026-03-16T11:45:00Z",
"manage_url": "https://app.tickory.app/dashboard/alerts",
"total_matches": 6,
"top_symbols": [
{
"symbol": "BTCUSDT",
"price": 84500.12,
"volume_quote": 1450000000
},
{
"symbol": "ETHUSDT",
"price": 4200.8,
"volume_quote": 980000000
}
]
}| Field | Type | Notes |
|---|---|---|
payload_version | string | Always 1.1 for aggregated legacy events. |
event_type | string | summary or throttle_summary. |
scan_name, scan_run_id | string | Names the originating scan and run. |
timestamp, manage_url | string | Delivery timestamp and manage-alerts link. |
total_matches, top_symbols | number, array | Present on summary events. |
suppressed_count, daily_limit, sent_today, reset_at | number, string | Present on throttle_summary when a daily limit is hit. |
OpenClaw Preset
The OpenClaw preset normalizes every event into the same envelope and adds explicit version fields. Choose this if you want a stable contract for receivers that process both single and summary alerts.
{
"payload_version": "2.0",
"contract_version": "tickory.openclaw.trigger.v1",
"event_id": "evt_01hsw3d6a2n9xq",
"idempotency_key": "scan:user:BTCUSDT:1773661500",
"event_type": "alert",
"scan_id": "scan_01hsw3b2k8jv0m",
"scan_name": "Momentum Reversal",
"scan_run_id": "run_01hsw3d6a2n9xq",
"symbol": "BTCUSDT",
"matched_at": "2026-03-16T11:45:00Z",
"sent_at": "2026-03-16T11:45:01Z",
"evidence_summary": {
"expression": "has_rsi_14 && rsi_14 < 30",
"timeframe": "1m",
"total_matches": 0,
"top_symbols": [],
"suppressed_count": 0,
"daily_limit": 0,
"sent_today": 0,
"reset_at": "",
"metrics": {
"price": 84500.12,
"rsi_14": 28.3,
"volume_quote": 1450000000,
"matched_metrics": {
"rsi_14": 28.3,
"close": 84500.12,
"volume_quote": 1450000000
}
}
},
"data": {
"exchange": "binance",
"contract_type": "perp",
"chart_url": "https://www.binance.com/en/futures/BTCUSDT",
"manage_url": "https://app.tickory.app/dashboard/alerts",
"timestamp_unix": 1773661500
}
}Common OpenClaw fields:
contract_versionis currentlytickory.openclaw.trigger.v1.event_ididentifies the delivery event.idempotency_keystays stable across retries.event_typeisalert,summary, orthrottle_summary.evidence_summaryholds expression, timeframe, aggregation details, and matched metrics.dataholds exchange, contract type, chart URL, manage URL, and Unix timestamp.
Aggregation fields vary by event type
total_matches, top_symbols, and throttle-related values live inside evidence_summary. For plain alert events those fields are usually zero or empty; they become meaningful on summary and throttle_summary.Signing & Verification
Tickory always sends JSON with a fixed User-Agent and a delivery timestamp. When webhook signing is enabled for your deployment, Tickory also sends HMAC headers you can verify on receipt.
Content-Type: application/jsonUser-Agent: Tickory/1.0X-Tickory-Timestampas a Unix timestamp stringX-Tickory-Event-IDwhen the payload includesevent_idIdempotency-Keywhen the payload includesidempotency_keyX-Tickory-Signature-Version: v1when signing is enabledX-Tickory-Signature: v1=<hex_hmac>when signing is enabled
Tickory signs the exact byte sequence "<unix_timestamp>.<raw_request_body>" using HMAC-SHA256.
Current signing model
WEBHOOK_EVENT_SIGNING_KEY, deliveries include X-Tickory-Signature and you verify against that deployment secret.Node.js
import crypto from "node:crypto";
function verifyTickorySignature({
rawBody,
signatureHeader,
timestampHeader,
signingSecret,
}: {
rawBody: Buffer;
signatureHeader: string;
timestampHeader: string;
signingSecret: string;
}) {
const expected = "v1=" + crypto
.createHmac("sha256", signingSecret)
.update(`${timestampHeader}.`)
.update(rawBody)
.digest("hex");
const actual = Buffer.from(signatureHeader, "utf8");
const wanted = Buffer.from(expected, "utf8");
if (actual.length !== wanted.length) {
return false;
}
return crypto.timingSafeEqual(actual, wanted);
}Python
import hashlib
import hmac
def verify_tickory_signature(raw_body: bytes, signature_header: str, timestamp_header: str, signing_secret: str) -> bool:
signed_payload = f"{timestamp_header}.".encode("utf-8") + raw_body
digest = hmac.new(
signing_secret.encode("utf-8"),
signed_payload,
hashlib.sha256,
).hexdigest()
expected = f"v1={digest}"
return hmac.compare_digest(signature_header, expected)Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func verifyTickorySignature(rawBody []byte, signatureHeader, timestampHeader, signingSecret string) bool {
mac := hmac.New(sha256.New, []byte(signingSecret))
mac.Write([]byte(timestampHeader))
mac.Write([]byte("."))
mac.Write(rawBody)
expected := "v1=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signatureHeader), []byte(expected))
}
func main() {
fmt.Println("verify in your HTTP handler")
}In production, also reject requests with stale X-Tickory-Timestamp values to protect against replay attacks.
Retry Behavior
- Success: any
2xxresponse marks the delivery successful. - Immediate HTTP retries: network failures and
5xxresponses are retried up to 3 times inside one worker attempt with short backoff. - Queued delivery retries: alert events are currently queued with 3 total worker attempts. If a worker attempt fails, Tickory schedules the next retry about 1 minute later, then about 5 minutes later before the final attempt.
- 4xx responses: these stop the immediate in-attempt retry loop, but the delivery still fails and may be retried later by the queue worker.
- Timeouts: expect roughly a 10-second timeout per outbound HTTP request attempt.
Because retries preserve Idempotency-Key and X-Tickory-Event-ID, receivers should deduplicate writes on one of those keys.
Testing
Use the built-in test alert after saving your webhook config. Test deliveries send an alertevent with sample market data and a synthetic symbol such as TEST-BTC.
For local development, expose your receiver with a tunnel because localhost and private-network targets are blocked by production SSRF protection.
ngrok http 3000- Run your local receiver on port
3000. - Start
ngrokand copy the public HTTPS URL. - Paste that URL into the Tickory webhook destination.
- Save the config and send a test alert.
- Inspect headers, raw body bytes, and signature verification in your local logs.
Recommended receiver behavior
2xx, and process the payload asynchronously behind your own queue if downstream work is expensive.Need broader alerting context first? See Alerts & Notifications.