Webhooks
Webhooks allow you to receive verification results asynchronously. When a verification completes — whether synchronously via the API or as part of a batch job — SignalStack sends an HTTP POST request to your configured endpoint.
Event types
| Event | Description |
|---|---|
verification.completed | A verification request has finished processing |
verification.failed | A verification request encountered an unrecoverable error |
batch.completed | All items in a batch verification have finished |
batch.partial | Some items in a batch succeeded, some failed |
credit.threshold | Your account has crossed a usage threshold (75%, 90%, 100%) |
Webhook payload format
{
"id": "wh_9f8e7d6c5b4a",
"type": "verification.completed",
"created_at": "2025-01-25T14:30:00Z",
"data": {
"request_id": "vrf_a1b2c3d4e5f6",
"request_type": "claim",
"trust_score": 0.97,
"verdict": "true",
"evidence": [
{
"source": "Wikipedia",
"url": "https://en.wikipedia.org/wiki/Example",
"supports_claim": true,
"authority_score": 0.92
}
],
"latency_ms": 187
}
}HMAC signature verification
Every webhook request includes a X-SignalStack-Signature header containing an HMAC-SHA256 signature of the raw request body, keyed with your webhook secret. Always verify this signature before processing the payload.
Python verification
import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
# In your webhook handler:
@app.post("/webhooks/signalstack")
async def handle_webhook(request):
payload = await request.body()
signature = request.headers.get("X-SignalStack-Signature")
if not verify_webhook(payload, signature, WEBHOOK_SECRET):
return {"error": "Invalid signature"}, 401
event = json.loads(payload)
# Process event...TypeScript verification
import { createHmac, timingSafeEqual } from "node:crypto"
function verifyWebhook(
payload: Buffer,
signature: string,
secret: string,
): boolean {
const expected = createHmac("sha256", secret)
.update(payload)
.digest("hex")
const expectedSig = `sha256=${expected}`
try {
return timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSig),
)
} catch {
return false
}
}Retry logic
SignalStack retries failed webhook deliveries up to 5 times with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | 10 seconds |
| 2 | 1 minute |
| 3 | 10 minutes |
| 4 | 1 hour |
| 5 | 6 hours |
Your endpoint must respond with a 2xx status within 10 seconds for the delivery to be considered successful. Any other status code, a timeout, or a network error triggers the next retry attempt. After all 5 attempts fail, the event is discarded. Webhook deliveries can be replayed from the dashboard.
Rate of delivery
To protect your server, SignalStack delivers webhooks at a maximum rate of 10 events per second per endpoint. If your endpoint responds slowly, the delivery rate is automatically throttled.
Configuration
Webhooks are configured through the dashboard or via the API:
# Configure webhook via API
curl -X POST https://signal-stack-ten.vercel.app/v1/webhooks -H "Authorization: Bearer $SIGNALSTACK_API_KEY" -H "Content-Type: application/json" -d '{
"url": "https://api.myapp.com/webhooks/signalstack",
"events": ["verification.completed", "verification.failed"],
"secret": "whsec_your_secret_here"
}'| Field | Required | Description |
|---|---|---|
url | Yes | HTTPS endpoint that receives webhook POST requests |
events | Yes | Array of event types to subscribe to |
secret | Recommended | Used for HMAC signature generation; must be at least 32 characters |
Testing webhooks
Use the SignalStack dashboard to send test events to your endpoint. You can also use tools likengrok to expose your local server during development.
Best practices
- Always verify the HMAC signature before processing
- Respond with 2xx quickly (within 10s) — process the event asynchronously
- Idempotent processing: use the
idfield to deduplicate events - Store the raw payload before any transformation for replay/debugging
- Rotate your webhook secret periodically
Next steps
- Review Error Handling
- Explore the API Reference
- Check Rate Limits