Skip to main content

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:

  • t is more than 5 minutes in the past or future.
  • v1 does 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

EventWhen it fires
report.createdA fake-account report is filed against the owner.
report.resolvedA fake-account report is closed (confirmed or rejected).
verification.grantedA verification request transitions to verified.
verification.revokedA previously granted verification is revoked.
link.click_thresholdA link crosses the user-configured click threshold.
link.createdA new link or Smart Route is created.
link.updatedA link's destination or settings change.
link.deletedA link is deleted.
theme.appliedThe user applies a new theme.
identity.score_changedThe user's identity score crosses a threshold.
risk.score_changedThe user's risk score crosses a threshold.
plan.changedThe 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.

Next