CS2 match alerts by webhook.
Register an HTTPS endpoint in the Cito dashboard, pick events such as cs2.match.completed or cs2.round.ended, and the API POSTs a signed JSON payload to you when they happen. Verify X-Cito-Signature (HMAC-SHA256 of the raw body), answer 2xx fast, and dedupe on X-Cito-Event-Id.
- 16 CS2 events
- HMAC-SHA256 signed
- Pro and Scale plans
{ "success": true, "available_on_plans": [ "PRO", "BUSINESS" ], "events": [ "cs2.match.started", "cs2.round.ended", "cs2.map.completed", "cs2.match.completed" ... ], "signature_headers": [ "X-Cito-Signature", "X-Cito-Timestamp" ]}Which CS2 events can trigger a webhook?
Sixteen events cover the life of a match, from cs2.match.started through every round to cs2.match.completed, plus ranking updates. GET /cs2/webhooks/events returns the current list, the signing contract and an example payload.
| Stage | Events |
|---|---|
| Match | cs2.match.started, cs2.match.live_started, cs2.match.live_ended, cs2.match.completed |
| Map | cs2.map.started, cs2.map.completed |
| Round | cs2.round.started, cs2.round.ended, cs2.score.updated |
| Bomb | cs2.bomb.planted, cs2.bomb.defused, cs2.bomb.exploded |
| Players and state | cs2.player.state_changed, cs2.live.state, cs2.live.player_stats_updated |
| Rankings | cs2.ranking.updated |
Step 1: How do I create a webhook?
Open Webhooks in the Cito dashboard, add your HTTPS URL and the CS2 events you want, and copy the webhook's signing secret into an environment variable. Webhooks are included on the Pro and Scale plans.
Dashboard: citoapi.com/dashboard/webhooks. Every delivery carries these headers: X-Cito-Signature, X-Cito-Event, X-Cito-Event-Id, X-Cito-Timestamp and X-Cito-Delivery-Attempt.
Step 2: How do I verify the signature?
Compute HMAC-SHA256 over the exact raw request body with your secret, hex-encode it, and compare it with X-Cito-Signature using a constant-time comparison. Do it before parsing the JSON; re-serialising changes the bytes and the check fails.
import crypto from "node:crypto"
import express from "express"
const app = express()
const SECRET = process.env.CITO_WEBHOOK_SECRET
const seen = new Set() // use Redis or your DB in production
app.post("/cito/webhook", express.raw({ type: "application/json" }), (req, res) => {
const expected = crypto.createHmac("sha256", SECRET).update(req.body).digest("hex")
const got = req.get("X-Cito-Signature") ?? ""
const ok = got.length === expected.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))
if (!ok) return res.sendStatus(401)
const id = req.get("X-Cito-Event-Id")
if (seen.has(id)) return res.sendStatus(200) // retried delivery, already handled
seen.add(id)
res.sendStatus(200) // answer first, work after
const { event, data } = JSON.parse(req.body.toString("utf8"))
if (event === "cs2.match.completed") notify(data)
})
function notify(data) { console.log("match completed", data) }
app.listen(3000)import hashlib, hmac, os, json
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["CITO_WEBHOOK_SECRET"].encode()
@app.post("/cito/webhook")
def cito_webhook():
body = request.get_data() # raw bytes, before parsing
expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-Cito-Signature", "")):
abort(401)
payload = json.loads(body)
print(payload["event"], payload["data"])
return "", 200Step 3: How do I post the alert to Discord or Slack?
Inside the handler, turn the payload into a message and POST it to the channel's incoming-webhook URL. Keep that call after you have answered Cito with 200, so a slow chat API never makes the delivery look failed.
async function notify(data) {
await fetch(process.env.DISCORD_WEBHOOK_URL, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ content: "CS2 match finished: " + JSON.stringify(data) }),
})
}What if I am on the free plan?
Use the Server-Sent Events stream at /api/v1/cs2/live/stream, which every plan can open, or poll GET /cs2/matches/results on a timer. Both work without a public HTTPS endpoint, which also makes them easier while developing.
Frequently asked questions
Something missing? Email support@citoapi.com.
Does the CS2 API support webhooks?
Yes, on the Pro and Scale plans: 16 CS2 events, from cs2.match.started and cs2.round.ended to cs2.match.completed and cs2.ranking.updated.
How are CS2 API webhooks signed?
HMAC-SHA256 of the exact raw JSON body with your webhook secret, hex-encoded in the X-Cito-Signature header, with no prefix.
Can a webhook be delivered twice?
Treat it as possible: X-Cito-Delivery-Attempt counts attempts, and X-Cito-Event-Id is stable across them, so dedupe on the event id.
How fast do webhooks arrive?
Cito's dispatcher delivered first attempts a median 1.2 s after the event was recorded (all games, 24 hours to 3 October 2026). CS2 live data itself refreshes every 15 seconds at the median.
Related
- CS2 Discord bot APILive score and result commands for a Discord bot.
- CS2 live score APISeries, map and round score for every match in progress.
- WebSocket disconnects mid-matchHeartbeats, reconnect with backoff, and state back on resubscribe.
- CS2 API pricingFree, Starter, Pro and Scale: requests, live delay and history per plan.
Build CS2 live score apps
Free is for building and testing. Paid plans add commercial use, real-time live data and the full archive.