Webhooks
Webhooks let your client (or any HTTPS endpoint) get notified when something changes on a user's Shuuka account, instead of polling the MCP server.
Webhooks are part of the Shuuka MCP surface — manage them through the webhooks:read and webhooks:write scopes.
Subscribe
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "subscribe_webhook",
"arguments": {
"url": "https://my-app.example/shuuka/webhook",
"events": ["report.created", "verification.granted", "link.click_threshold"],
"description": "Brand-safety pipeline"
}
}
}
Response:
{
"id": "whk_...",
"url": "https://my-app.example/shuuka/webhook",
"events": [...],
"secret": "shk_whk_..."
}
The secret is shown once. Store it. Use tools/call with list_my_webhooks to enumerate, and delete_webhook to remove.
Event payload
Every delivery is a JSON POST with these headers:
X-Shuuka-Event: report.created
X-Shuuka-Event-Id: evt_...
X-Shuuka-Delivery-Id: dlv_...
X-Shuuka-Signature: t={timestamp},v1={hmac_sha256}
Content-Type: application/json
Body:
{
"id": "evt_...",
"type": "report.created",
"created_at": "2026-05-02T14:00:00Z",
"data": {
/* event-specific shape */
}
}
Verifying signatures
The X-Shuuka-Signature header is t=<unix-timestamp>,v1=<hex-hmac-sha256>.
Compute:
v1 = hex( hmac_sha256(secret, t + "." + raw_body) )
Reject any request where:
tis more than 5 minutes in the past or future.v1does not match.
Node.js example:
const crypto = require('crypto');
function verify(req, secret) {
const sig = req.headers['x-shuuka-signature'] || '';
const parts = Object.fromEntries(sig.split(',').map(p => p.split('=')));
const t = parseInt(parts.t, 10);
if (!t || Math.abs(Date.now()/1000 - t) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${req.rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1 || '')
);
}
Retries
- Failed deliveries (HTTP 4xx that aren't 410, all 5xx, network errors) retry with exponential backoff: 1m, 5m, 30m, 2h, 12h, 24h.
- HTTP 410 Gone is treated as a permanent unsubscribe — the webhook is auto-disabled.
- After 6 failures over 24 hours the webhook is disabled. The user sees it in Settings → Webhooks with a "Re-enable" button.
Event types
| Event | When it fires |
|---|---|
report.created | A fake-account report is filed against the owner. |
report.resolved | A fake-account report is closed (confirmed or rejected). |
verification.granted | A verification request transitions to verified. |
verification.revoked | A previously granted verification is revoked. |
link.click_threshold | A link crosses the user-configured click threshold. |
link.created | A new link or Smart Route is created. |
link.updated | A link's destination or settings change. |
link.deleted | A link is deleted. |
theme.applied | The user applies a new theme. |
identity.score_changed | The user's identity score crosses a threshold. |
risk.score_changed | The user's risk score crosses a threshold. |
plan.changed | The user upgrades, downgrades, or cancels a plan. |
data shapes follow the same schema as the corresponding MCP tool's structuredContent.
Delivery log
Use list_webhook_deliveries (under webhooks:read) or open Settings → Webhooks → Deliveries to see every attempt with status, timestamps, request body, and response body. Logs retain 30 days.
Plan availability
Webhooks are available on all plans. Free users are subject to the same monthly visit traffic gate that applies to other Pro features — over the threshold, deliveries pause until next month or an upgrade.