← All posts
Written by Suleman·Published July 20, 2026·11 min read
Monitor Crypto Prices via API and Fire Webhook Alerts on Any Percent Move

Monitor Crypto Prices via API and Fire Webhook Alerts on Any Percent Move

Crypto markets don't sleep, and neither does the pain of building your own price monitoring stack. You wire up a polling loop against the CoinGecko API, store the last known price in a database, diff the value on each tick, and then figure out how to actually deliver the alert - all before you've written a single line of business logic. That setup breaks quietly. The VPS reboots. The cron job silently exits. Your TLS cert expires on a weekend.

This guide covers a cleaner path: using Verid's web change detection API to monitor cryptocurrency prices against CoinGecko, apply percent-move thresholds, and fire HMAC-signed webhook alerts to your own backend, Slack, or Discord - without maintaining any of that polling infrastructure yourself.

What "crypto price monitoring" actually means for a developer

There are two completely different things people mean when they say they want crypto price monitoring.

The first is a consumer concern: "Tell me when BTC hits $50k." That's what apps like exchange notification systems and portfolio trackers are built for. They're great for individuals and they lock you into their ecosystem.

The second is a developer concern: "When BTC drops 8% between polling cycles, fire a POST request to my trading bot's webhook endpoint with the before/after values as structured JSON, and don't fire again until the next meaningful move." That's what you're here for.

Most tools solve problem one. Verid solves problem two.

Why exchange-side alerts and consumer apps fall short

If you've already tried the usual options, you already know the gaps. But it's worth naming them precisely because the gap is exactly where Verid fits.

Exchange notifications are tied to your account on that venue. If you monitor Coinbase, you can only alert from Coinbase. Your trading bot, your internal dashboard, and your team Slack channel can't natively receive the signal without extra glue code.

Crypto alerting services like CryptocurrencyAlerting.com are genuinely useful for personal alerts via SMS or Telegram. But they're not built for developers who need a structured JSON payload delivered to a programmable endpoint, a before/after diff, and a cryptographic signature they can verify.

Building from scratch - polling CoinGecko, diffing values, storing state, handling retries - is the correct solution in theory, but it's also a persistent maintenance burden. The logic is simple but the infrastructure isn't.

The gap is this: you want the structured output and webhook delivery that only a developer-focused tool gives you, without having to wire up the whole loop yourself.

How Verid handles crypto price monitoring

Verid's core loop has five stages: fetch the URL, extract structured fields from the response, diff those fields against the previous run, evaluate a predicate against the diff, and deliver a signed webhook when the predicate returns true.

For crypto prices, this maps cleanly:

  • Fetch - Poll api.coingecko.com/api/v3/simple/price on your chosen interval
  • Extract - Pull price_usd, market_cap, and change_24h via JSONPath
  • Diff - Compare this run's price against the last successful run
  • Predicate - Only fire if price dropped by more than X percent (or crossed a custom rule)
  • Deliver - POST a signed JSON payload to your endpoint, Slack, or Discord

CoinGecko's free API returns numeric values directly - no currency symbols to strip. That matters because Verid's percent-change predicates require numeric fields to work correctly.

Setting up a Bitcoin price monitor

Via cURL

curl -X POST https://api.verid.dev/v1/monitors \
  -H "Authorization: Bearer $VERID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Bitcoin 5% Move Alert",
    "url": "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd&include_market_cap=true&include_24hr_change=true",
    "schedule_interval_seconds": 3600,
    "extract_config": {
      "method": "json_path",
      "fields": {
        "price_usd":  "$.bitcoin.usd",
        "market_cap": "$.bitcoin.usd_market_cap",
        "change_24h": "$.bitcoin.usd_24h_change"
      }
    },
    "diff_predicate": {
      "type": "field_decreases_by_percent",
      "field": "price_usd",
      "threshold": 5
    },
    "deliveries": [
      { "type": "webhook", "url": "https://your-app.com/webhooks/crypto" },
      { "type": "discord", "webhookUrl": "https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN" }
    ]
  }'

schedule_interval_seconds: 3600 checks once per hour. Drop it to 900 (15 minutes) on the Pro plan or 300 (5 minutes) on Scale. Your plan tier controls the minimum interval - check the pricing page to confirm what's available to you.

Via the Node.js SDK

npm install @verid.dev/sdk
import { VeridClient } from '@verid.dev/sdk';

const client = new VeridClient({
  apiKey: process.env.VERID_API_KEY!,
});

const monitor = await client.monitors.create({
  name: 'Bitcoin 5% Move Alert',
  url: 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd&include_market_cap=true&include_24hr_change=true',
  schedule_interval_seconds: 3600,
  extract_config: {
    method: 'json_path',
    fields: {
      price_usd:  '$.bitcoin.usd',
      market_cap: '$.bitcoin.usd_market_cap',
      change_24h: '$.bitcoin.usd_24h_change',
    },
  },
  diff_predicate: {
    type: 'field_decreases_by_percent',
    field: 'price_usd',
    threshold: 5,
  },
  deliveries: [
    { type: 'webhook', url: 'https://your-app.com/webhooks/crypto' },
  ],
});

console.log('Monitor created:', monitor.id);

Or use the pre-built template - it handles the extraction config automatically:

curl -X POST https://api.verid.dev/v1/monitors/from-template/crypto-price-coingecko \
  -H "Authorization: Bearer $VERID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "BTC 5% Move Alert",
    "deliveries": [
      { "type": "webhook", "url": "https://your-app.com/webhooks/crypto" }
    ]
  }'

The crypto-price-coingecko template comes pre-wired with the correct JSONPath fields. You only need to set your name and delivery targets.

Predicates: the logic layer that stops alert noise

The predicate is what makes this different from a raw polling loop. Instead of firing every time the price ticks, Verid evaluates your rule and only delivers when it returns true. Every run is still recorded - the silence is intentional.

Here's the full set of predicate types relevant to crypto monitoring:

PredicateWhat it fires onCrypto use case
field_decreases_by_percentPrice drops >= N%Dip alerts, stop-loss triggers
field_increases_by_percentPrice rises >= N%Breakout alerts, take-profit signals
field_decreases_by_absolutePrice drops by >= $NDollar-denominated threshold alerts
field_increases_by_absolutePrice rises by >= $NDollar-denominated upside alerts
field_changesAny change in a specific fieldNew ATH detection, status flips
field_equalsField matches an exact valueRound-number alerts (e.g., "price = 100000")
field_matches_regexNew value matches a regex patternPattern-based market condition detection
compositeAND or OR of any of the aboveComplex multi-condition rules

One thing worth noting: field_decreases_by_percent compares the current run's value against the previous run, not against an absolute high-water mark. If you run every hour and the price drops 4% per hour, each individual run won't fire a 5% threshold. Choose your interval to match the window you actually care about.

Composite predicates for two-sided alerts

Most real use cases want to know about moves in either direction. A 5% swing is a 5% swing regardless of which way it goes. Use a composite OR predicate:

{
  "type": "composite",
  "operator": "OR",
  "conditions": [
    { "type": "field_decreases_by_percent", "field": "price_usd", "threshold": 5 },
    { "type": "field_increases_by_percent", "field": "price_usd", "threshold": 5 }
  ]
}

For tighter rules - for example, only fire when the price drops and the 24h change is already negative (not a recovery bounce) - use AND:

{
  "type": "composite",
  "operator": "AND",
  "conditions": [
    { "type": "field_decreases_by_percent", "field": "price_usd", "threshold": 5 },
    { "type": "field_decreases_by_percent", "field": "change_24h", "threshold": 2 }
  ]
}

Composite conditions can be nested, so you can build fairly complex logic trees without writing any application code. The predicate evaluates server-side on each poll - you just write the JSON config once.

Composite predicates for two-sided alerts

What the webhook payload looks like

When a predicate fires, Verid sends a POST request to your endpoint with this structure:

{
  "id": "del_01H...",
  "version": "2026-05-01",
  "monitor_id": "9b1c...",
  "run_id": "a4f7...",
  "fired_at": "2026-05-08T13:42:00Z",
  "diff": {
    "fields_changed": ["price_usd", "change_24h", "market_cap"],
    "before": {
      "price_usd": 62450.12,
      "change_24h": -1.2,
      "market_cap": 1230000000000
    },
    "after": {
      "price_usd": 58901.40,
      "change_24h": -6.4,
      "market_cap": 1163000000000
    }
  },
  "monitor": {
    "url":  "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin...",
    "name": "Bitcoin 5% Move Alert"
  }
}

The before and after values give you everything you need without a second API call. Your trading bot, journal app, or alerting pipeline gets the full context in one payload.

Verifying the webhook signature

Every delivery includes a Verid-Signature header. Verify it before processing the payload - this prevents spoofed requests from hitting your endpoint.

Verid-Signature: t=1715175720,v1=a3f9c...

The signature is HMAC-SHA256 over {timestamp}.{raw_body} using your monitor's webhook secret. Verid includes a 5-minute timestamp tolerance window to handle clock drift.

Here's a production-ready Node.js handler:

import { createHmac, timingSafeEqual } from 'crypto';
import express from 'express';

const app = express();
app.use(express.raw({ type: 'application/json' }));

function verifyWebhook(
  header: string,
  rawBody: string,
  secret: string,
  toleranceSecs = 300,
): boolean {
  const parts = Object.fromEntries(
    header.split(',').map((p) => p.split('=')),
  );
  const timestamp = parseInt(parts['t'] ?? '0', 10);
  const signature = parts['v1'];

  if (!timestamp || !signature) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSecs) return false;

  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  return timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(signature, 'hex'),
  );
}

app.post('/webhooks/crypto', (req, res) => {
  const header = req.headers['verid-signature'] as string;
  const rawBody = req.body.toString();

  if (!verifyWebhook(header, rawBody, process.env.WEBHOOK_SECRET!)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const payload = JSON.parse(rawBody);
  const { before, after } = payload.diff;

  const priceDrop = (
    ((before.price_usd - after.price_usd) / before.price_usd) * 100
  ).toFixed(2);

  console.log(`BTC dropped ${priceDrop}%: $${before.price_usd} -> $${after.price_usd}`);

  // Your downstream logic here: trigger a trade, update a dashboard, log to a journal
  res.sendStatus(200);
});

Using timingSafeEqual instead of === prevents timing-based side-channel attacks. Always pass the raw request body (before JSON.parse) into the HMAC function - any whitespace change will break signature verification. The full webhooks documentation has verified implementations in Python, Ruby, Go, and PHP as well.

Monitoring multiple coins

To monitor Ethereum, Solana, or any other CoinGecko-supported coin, change the ids parameter and the JSONPath accordingly:

{
  "name": "ETH 5% Move Alert",
  "url": "https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd&include_24hr_change=true",
  "extract_config": {
    "method": "json_path",
    "fields": {
      "price_usd":  "$.ethereum.usd",
      "change_24h": "$.ethereum.usd_24h_change"
    }
  }
}

You can also pull multiple coins in a single API call by passing comma-separated IDs (ids=bitcoin,ethereum,solana) and then extracting with separate field paths:

{
  "url": "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum,solana&vs_currencies=usd",
  "extract_config": {
    "method": "json_path",
    "fields": {
      "btc_usd": "$.bitcoin.usd",
      "eth_usd": "$.ethereum.usd",
      "sol_usd": "$.solana.usd"
    }
  },
  "diff_predicate": {
    "type": "any_field_changes"
  }
}

With any_field_changes, the monitor fires whenever any of the three prices differs from the last run. Combine it with specific per-field predicates if you only want BTC moves to trigger.

CoinGecko rate limits: The free public tier has a per-minute request cap. Verid polls once per interval per monitor, so a single monitor checking every 5 minutes generates 12 requests per hour - well within the free tier's limits. Stacking 30+ monitors against the same endpoint at short intervals can push against the ceiling. Spread intervals slightly if you're running many coins on a tight schedule.

Polling interval vs. signal resolution

Your poll interval directly determines what moves you can detect. This is worth thinking about carefully before you configure a monitor.

PlanMinimum intervalSmallest detectable window
Free24 hoursDaily swing detection only
Starter1 hourHourly moves (most swing-trade alerts)
Pro15 minutesIntraday moves, short-term volatility
Scale5 minutesNear-real-time for high-frequency signals

A 5% threshold on a 15-minute interval means: the price must drop 5% within a single 15-minute polling window to fire the predicate. A slow bleed of 1% per hour for 6 hours won't trigger it - each diff is only against the previous run, not an absolute baseline. Design your thresholds with the interval in mind. See the full plan comparison for interval limits per tier.

Delivery channels

When a predicate fires, Verid can push the diff to multiple destinations simultaneously:

  • Webhook - HMAC-signed POST to any HTTPS endpoint you control. Your trading bot, internal service, or a serverless function.
  • Slack - The before/after diff lands in any channel. No Slack app to install.
  • Discord - Same field-level alert, routed to a Discord webhook URL.
  • Email - A plain readable summary, no dashboard login required.

Multiple delivery targets are supported in one monitor. You can simultaneously POST to your app, drop a Slack message to your trading team, and log to a Discord channel for community visibility - all from the same predicate firing.

Failed deliveries don't disappear. Verid retries up to six times with exponential backoff (5 minutes, 15 minutes, 30 minutes, 1 hour, 2 hours), then moves the delivery to a dead-letter queue. You can replay it from the dashboard or via POST /v1/deliveries/:id/replay. Details in the notifications documentation.

Veird vs DIY

How Verid compares to the alternatives

CapabilityDIY (cron + polling)Crypto alert appsVerid
Structured before/after diffYou build itNoYes, in every payload
Percent-move predicatesYou write the logicYes (consumer-focused)Yes, configurable via API
HMAC-signed webhookYou build itRareYes, every delivery
Retry + dead-letter queueYou build itNoYes, 6 retries + DLQ
Multi-coin in one monitorYou build itNoYes, via multi-field JSONPath
Composite AND/OR conditionsYou build itNoYes, nested predicates
Maintenance burdenHigh (your infra)Low (their infra, their rules)Low (their infra, your rules)
Works with your own endpointYesSometimesYes, natively

The core difference: consumer alert apps let you configure their system. Verid gives you structured output and delivers to your systems, not theirs.

Common mistakes to avoid

A few patterns that cause problems in practice:

Using string-formatted prices with percent predicates. If your extraction returns "$62,450.12" instead of 62450.12, the percent-change predicate won't evaluate correctly. CoinGecko's JSON API returns bare numbers for the usd field, so this isn't usually an issue there - but watch for it on any exchange or aggregator that formats prices as strings.

Setting the threshold too low for the interval. A 1% threshold on a 1-hour interval will fire constantly on any volatile day. Match your threshold to what would require meaningful action - 5-10% is reasonable for hourly monitoring of major coins.

Not storing the monitor ID. After you create a monitor, save the returned id. You'll need it to update the predicate, pause the monitor, or retrieve run history via the API.

Processing webhooks synchronously. Your webhook endpoint should return 200 immediately and process the payload in a background job. If your endpoint takes more than a few seconds to respond, Verid treats it as a failure and retries. A slow database write or an external API call inside the request handler will trigger the retry logic unnecessarily.

Try it yourself

The fastest way to see this working is the free plan - 5 monitors, no credit card, the full extraction and delivery loop. Set up a Bitcoin percent-move monitor in about a minute using the crypto-price-coingecko template, point it at a test webhook endpoint (webhook.site works well for initial testing), and watch the first payload arrive.

From there, you can layer in composite predicates for two-sided alerts, add Slack or Discord as secondary delivery targets, or extend the same pattern to stock price monitoring, FX rate tracking, or any other JSON API field monitoring use case.

Get your free API key at verid.dev and have a working crypto price alert running before the next candle closes.

Frequently Asked Questions

Can I monitor altcoins and not just Bitcoin or Ethereum?

Yes. Any coin that has a valid id in CoinGecko's API can be monitored. Look up the coin ID at api.coingecko.com/api/v3/coins/list - it's a simple snake_case string like solana, chainlink, or uniswap. Plug it into the ids query parameter and update your JSONPath fields accordingly.

What happens if CoinGecko's API is down during a polling cycle?

Verid records the failed run but doesn't fire deliveries on extraction failures. The predicate only evaluates when a successful extraction produces comparable fields. Stale runs don't reset your diff baseline either - the next successful run compares against the last successful baseline, not the failed one.

How is this different from setting up an alert directly on a crypto exchange?

Exchange-native alerts notify you via their own channels and are scoped to their platform. Verid polls a neutral public data source (CoinGecko), delivers a signed structured payload to any endpoint you control, and fires for any coin regardless of which exchange you use. Your downstream systems - trading bots, dashboards, audit logs, team chats - receive the signal in a consistent format they already understand.

Does Verid store my extracted price data over time?

Yes. Each run is recorded with its extracted field values, and you can query run history via the API. The retention period depends on your plan: 14 days on Free, 180 days on Starter, 365 days on Pro, and 2 years on Scale. This gives you an audit trail of when the predicate fired and what the before/after values were for every delivery.

Track competitor prices automatically

Set up a competitor price-drop monitor in 60 seconds — 5 monitors free, no credit card.