Price alerts, as JSON.
Every coinsentry alert can POST a signed JSON payload to any endpoint you own — a trading bot, a spreadsheet, a Discord relay, your home dashboard. This is the whole integration: the exact payload, signature verification in two languages, and the delivery semantics. Ten minutes, start to verified.
What lands on your endpoint
One shape, always. Prices are strings — exact numerics as exchange APIs send them, so you never lose precision to a float. Timestamps are unix milliseconds. Test deliveries (event: "test") use the exact production shape — build your parser against the test event and production just works.
{
"event": "price_alert",
"alert": {
"pairSymbol": "BTC-USDT",
"exchange": "Binance",
"currentPrice": "120014.37",
"targetPrice": "120000.00",
"alertType": "crossing-up",
"triggeredAt": 1753948800412
},
"timestamp": 1753948800498
}| TYPE | MEANING | |
|---|---|---|
| event | "price_alert" | "test" | Test fires from the dashboard's Send test button — identical shape. |
| alert.pairSymbol | string | The pair that crossed, e.g. "BTC-USDT". |
| alert.exchange | string | The exchange whose feed triggered — alerts are pinned to one exchange's real prices. |
| alert.currentPrice | string | Price at trigger time. String on purpose — parse with a decimal type if precision matters. |
| alert.targetPrice | string | The level you set. |
| alert.alertType | string | Direction of the cross, e.g. "crossing-up". |
| alert.triggeredAt | number | Unix ms — when the price crossed. |
| timestamp | number | Unix ms — when this delivery was sent. Differs from triggeredAt on retries. |
Verify it's really us
Set a secret in Dashboard → Channels → Webhook and every delivery carries an X-Coinsentry-Signature header: the hex-encoded HMAC-SHA256 of the request body, keyed with your secret. The body is serialized once, signed, and shipped as-is — so the signature always matches the exact bytes you receive.
The one classic mistake: verifying against re-serialized JSON. Key order or whitespace differences break the digest. Always verify against the raw request bytes, before any JSON parsing.
import crypto from "node:crypto";
// rawBody must be the exact bytes received — not re-serialized JSON.
// (Express: use express.raw() or the verify hook on express.json().)
function isFromCoinsentry(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return (
signatureHeader.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))
);
}import hashlib
import hmac
# raw_body must be the exact request bytes — not re-serialized JSON.
def is_from_coinsentry(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)Delivery rules to build against
- Method & headers are yours. POST, PUT or PATCH, plus any custom headers — an auth token for your own gateway, a routing hint, whatever your stack needs.
- 10-second timeout. Respond 2xx fast; queue slow work. A timeout counts as a failed delivery.
- Failed deliveries retry automatically. Which means at-least-once delivery: the same alert can arrive twice. Deduplicate on
alert.triggeredAt+alert.pairSymbolif double-handling would hurt. - Redirects are not followed. Point the URL at the final endpoint.
Verified in three steps
- 01Point it somewhere
Grab a throwaway URL from webhook.site, paste it in Dashboard → Channels → Webhook, add a secret.
- 02Send the test event
Hit Send test. The test payload is the production shape — inspect the body and the X-Coinsentry-Signature header live.
- 03Verify and ship
Drop in the verification snippet, swap the URL for your real endpoint, route any alert to the webhook channel.
Fair questions, straight answers
Set a secret in your webhook settings. Every delivery then carries an X-Coinsentry-Signature header: the hex HMAC-SHA256 of the exact request body, keyed with your secret. Recompute it over the raw bytes you received and compare with a constant-time check.
A JSON object: event ("price_alert" or "test"), an alert object with pairSymbol, exchange, currentPrice, targetPrice (both strings), alertType and triggeredAt (unix ms), plus a top-level timestamp. Test deliveries use the exact production shape, so parse once.
Yes — POST, PUT or PATCH, plus any custom headers you need (an auth token for your own endpoint, say). Content-Type and the signature header are always set by coinsentry and can't be overridden.
Failed deliveries are retried automatically. Your endpoint has 10 seconds to respond; respond with a 2xx quickly and do slow work asynchronously. Make handling idempotent — a retry means the same alert can arrive more than once.
No. Webhooks are outbound from coinsentry to your URL — you configure the URL, optional secret, method and headers in the dashboard. No polling, no API key, no exchange account connection.