Build on the click-fraud platform.
REST API and webhooks for sites, blocked IPs, and click intelligence. Bearer-token auth, HMAC-signed payloads, cursor pagination. Available on Pro and above.
- Endpoints
- 7
- Rate limit
- 60/min
- Webhook events
- 7
- Signature
- HMAC
Quickstart#
- 1Open Settings → API & Webhooks in your dashboard.
- 2Create a token, pick the abilities you need, and copy it once — it will not be shown again.
- 3Send requests with
Authorization: Bearer YOUR_TOKEN.
curl https://api.myclickshield.com/api/v1/sites \
-H 'Authorization: Bearer YOUR_TOKEN'Authentication#
Every request must include a bearer token in the Authorization header.
Authorization: Bearer mcs_live_...Tokens are scoped to one account, can be granted a subset of abilities, and can be set to expire after 30, 90, 180, 365 or 730 days. Lost tokens cannot be recovered — revoke and create a new one. Maximum 20 tokens per account.
Token abilities#
Each token carries a set of abilities. A request fails with 403 Forbidden if the token doesn't carry the ability the endpoint requires.
| Ability | Description |
|---|---|
| sites:read | List and read sites the token owner can access |
| stats:read | Read aggregate fraud statistics for a site |
| clicks:read | Read individual click events |
| blocked-ips:read | List blocked IPs on a site |
| blocked-ips:write | Add and remove blocked IPs |
Rate limits#
60 requests per minute per token. The standard rate-limit headers are returned on every response; over-limit requests get a 429 Too Many Requests.
X-RateLimit-Limit
Window cap
X-RateLimit-Remaining
Requests left
Retry-After
Seconds to wait
Errors#
All errors return a JSON body of the shape { "message": "..." }.
| Status | Meaning |
|---|---|
| 401 | Missing or invalid token |
| 402 | Plan does not include API access — upgrade to Pro+ |
| 403 | Token lacks the required ability |
| 404 | Resource not found (or not owned by the caller) |
| 422 | Validation failed — see errors field |
| 429 | Rate limit exceeded |
Endpoints
7 endpointsList sites
Returns all sites the token owner can access (their own + team sites).
Required abilitysites:read
curl https://api.myclickshield.com/api/v1/sites \
-H 'Authorization: Bearer YOUR_TOKEN'Response · 200 OK
{
"data": [
{
"id": 17,
"name": "Acme Coffee",
"domain": "acme-coffee.com",
"is_active": true,
"grace_mode": false,
"block_threshold": 70,
"flag_threshold": 40,
"created_at": "2026-04-01T12:00:00+00:00"
}
]
}Retrieve a site
Returns one site by ID. 404 if the token owner cannot access it.
Required abilitysites:read
Parameters
| Name | Type | Description |
|---|---|---|
| id* | integer | Site ID |
Aggregate stats
Click counts and fraud rate for a window of recent days.
Required abilitystats:read
Parameters
| Name | Type | Description |
|---|---|---|
| days | integer (1-90) | Window size in days (default 30) |
Response · 200 OK
{
"data": {
"site_id": 17,
"window_days": 30,
"total_clicks": 4128,
"blocked_clicks": 312,
"flagged_clicks": 87,
"fraud_rate": 7.56
}
}List clicks
Cursor-paginated click events. Use the returned next_cursor for the next page.
Required abilityclicks:read
Parameters
| Name | Type | Description |
|---|---|---|
| status | enum | valid | flagged | blocked |
| classification | string | Internal classification label |
| ip | string | Filter to clicks from one IP |
| since | date | ISO-8601 lower bound on created_at |
| until | date | ISO-8601 upper bound on created_at |
| per_page | integer (1-200) | Page size (default 50) |
| cursor | string | Cursor returned by the previous page |
List blocked IPs
Cursor-paginated list of IPs currently blocked on the site.
Required abilityblocked-ips:read
Block an IP
Idempotent — re-blocking an existing IP updates its metadata.
Required abilityblocked-ips:write
Parameters
| Name | Type | Description |
|---|---|---|
| ip_address* | IPv4/v6 | IP to block |
| reason | string | Free-text reason |
| type | enum | permanent | temporary |
| expires_at | datetime | For temporary blocks |
curl -X POST https://api.myclickshield.com/api/v1/sites/17/blocked-ips \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"ip_address":"203.0.113.42","reason":"Manual review"}'Unblock an IP
Removes the block record. Subsequent clicks from that IP will be evaluated normally.
Required abilityblocked-ips:write
Webhooks
7 eventsOverview#
Subscribe to events and receive a signed POST whenever they happen — no polling required. Configure endpoints under Settings → API & Webhooks. You can have up to 10 endpoints per account.
Each delivery is a JSON POST with these headers:
X-MyClickShield-EventEvent name (e.g. ip.blocked)X-MyClickShield-TimestampUnix timestamp in secondsX-MyClickShield-SignatureHMAC-SHA256 hex digestX-MyClickShield-DeliveryUnique delivery ID for idempotency
POST /your-endpoint HTTP/1.1
Content-Type: application/json
X-MyClickShield-Event: ip.blocked
X-MyClickShield-Timestamp: 1746367200
X-MyClickShield-Signature: 9f7c4...
X-MyClickShield-Delivery: 5821
{
"event": "ip.blocked",
"data": {
"site_id": 17,
"site_domain": "acme-coffee.com",
"ip_address": "203.0.113.42",
"reason": "Auto-blocked due to high fraud score",
"fraud_score": 88,
"source": "auto"
},
"sent_at": "2026-05-04T12:00:00+00:00"
}Verifying signatures#
Compute HMAC_SHA256(secret, timestamp + "." + raw_body) and compare in constant time. Reject if the signature mismatches or the timestamp is more than 5 minutes old (replay protection).
import crypto from 'crypto'
app.post('/webhooks/myclickshield', express.raw({ type: 'application/json' }), (req, res) => {
const ts = req.headers['x-myclickshield-timestamp']
const sig = req.headers['x-myclickshield-signature']
const body = req.body.toString()
const expected = crypto
.createHmac('sha256', process.env.MCS_WEBHOOK_SECRET)
.update(ts + '.' + body)
.digest('hex')
if (
sig.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig)) ||
Math.abs(Date.now() / 1000 - Number(ts)) > 300
) {
return res.status(400).send('invalid signature')
}
const payload = JSON.parse(body)
// ... handle payload.event ...
res.sendStatus(200)
})Event catalog#
| Event | When |
|---|---|
| ip.blocked | An IP was added to a site's block list (manual, API, or auto-block) |
| ip.unblocked | An IP was removed from a site's block list |
| fraud.detected | A click was classified as fraudulent |
| site.created | A new site was added to the account |
| site.paused | Auto-pause kicked in and ad spend was paused for a site |
| site.resumed | Auto-pause expired and ads resumed |
| subscription.updated | Subscription plan, status, or click limit changed |
Retries & auto-disable#
Non-2xx responses and transport errors trigger up to 5 attempts with exponential back-off:
- immediate→
- 30s→
- 2m→
- 5m→
- 30m
After 20 consecutive failures the endpoint is auto-disabled — re-enable it from the dashboard once you've fixed your receiver. A 2xx reply confirms delivery; we don't expect any specific body.
Ready to integrate?
Generate a token in your dashboard and start with the quickstart above.
Open dashboard