Webhooks
Receive real-time notifications when important events occur. Webhooks allow you to react immediately to blocked requests, failed challenges, and other security events.
Setting Up Webhooks
Configure Webhook
/v1/webhooks/configConfigure Webhook
Set up webhook notifications for your organization.
curl -X POST "https://ag.runloci.com/v1/webhooks/config" \
-H "x-org-id: your_org_id" \
-H "x-api-key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-backend.com/webhooks/accessgate",
"secret": "whsec_your_webhook_secret",
"events": ["risk.high", "risk.low"],
"enabled": true
}'
Response: 200 OK
{
"url": "https://your-backend.com/webhooks/accessgate",
"events": ["risk.high", "risk.low"],
"enabled": true,
"created_at": "2026-01-25T10:30:00Z"
}
Available Events
| Event | Description | When Triggered |
|---|---|---|
risk.high |
High-risk request detected | Score >= block threshold |
risk.low |
Low-risk request | Score below review threshold |
challenge.passed |
Challenge completed | User verified successfully |
challenge.failed |
Challenge failed | Wrong code or expired |
Subscribe only to events you need. This reduces noise and improves webhook reliability.
Webhook Payload Format
All webhook events follow this format:
{
"id": "evt_xyz789",
"type": "risk.high",
"created_at": "2026-01-25T10:30:00Z",
"data": {
"request_id": "req_abc123",
"entity_id": "user_123",
"decision": {
"outcome": "block",
"score": 87,
"reasons": ["Impossible travel detected", "New device detected"]
},
"context": {
"action": "login",
"ip": "102.88.34.45",
"email": "user@example.com"
}
}
}
Event Payloads
risk.high
{
"id": "evt_001",
"type": "risk.high",
"created_at": "2026-01-25T10:30:00Z",
"data": {
"request_id": "req_abc123",
"entity_id": "user_123",
"decision": {
"outcome": "block",
"score": 87,
"reasons": ["Impossible travel detected", "New device detected"]
},
"context": {
"action": "login",
"ip": "192.0.2.1",
"email": "user@example.com",
"session_id": "sess_xyz"
}
}
}
challenge.failed
{
"id": "evt_003",
"type": "challenge.failed",
"created_at": "2026-01-25T10:35:00Z",
"data": {
"challenge_id": "ch_ghi789",
"entity_id": "user_123",
"type": "email_otp",
"reason": "max_attempts_exceeded",
"original_request_id": "req_abc123"
}
}
Verifying Webhook Signatures
All webhooks are signed using HMAC-SHA256. Verify signatures to ensure webhooks are from AccessGate.
Signature Header
X-AccessGate-Signature: sha256=abc123def456...
X-AccessGate-Timestamp: 1706180400
Verification (Node.js)
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, timestamp, secret) {
const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expectedSignature}`)
);
}
// Express middleware
app.post('/webhooks/accessgate', express.json(), (req, res) => {
const signature = req.headers['x-accessgate-signature'];
const timestamp = req.headers['x-accessgate-timestamp'];
if (!verifyWebhookSignature(req.body, signature, timestamp, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook
const event = req.body;
console.log(`Received ${event.type} event`);
res.status(200).json({ received: true });
});
Verification (Python)
import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = 'whsec_your_secret'
def verify_signature(payload, signature, timestamp, secret):
signed_payload = f"{timestamp}.{payload}"
expected = hmac.new(
secret.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, f"sha256={expected}")
@app.route('/webhooks/accessgate', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-AccessGate-Signature')
timestamp = request.headers.get('X-AccessGate-Timestamp')
if not verify_signature(request.data.decode(), signature, timestamp, WEBHOOK_SECRET):
return jsonify({'error': 'Invalid signature'}), 401
event = request.json
print(f"Received {event['type']} event")
return jsonify({'received': True}), 200
Always verify webhook signatures before processing. Never trust unverified webhooks.
Handling Webhooks
Best Practices
1. Respond Quickly
Return a 200 status immediately. Process the event asynchronously.
app.post('/webhooks/accessgate', (req, res) => {
// Acknowledge immediately
res.status(200).json({ received: true });
// Process asynchronously
processWebhookAsync(req.body);
});
async function processWebhookAsync(event) {
switch (event.type) {
case 'risk.high':
await notifySecurityTeam(event.data);
break;
case 'challenge.failed':
await lockAccount(event.data.entity_id);
break;
}
}
2. Handle Retries If your endpoint returns a non-2xx status, we'll retry:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 5 seconds |
| 3 | 30 seconds |
| 4 | 2 minutes |
| 5 | 10 minutes |
Use idempotency keys to prevent duplicate processing:
const processedEvents = new Set();
app.post('/webhooks/accessgate', (req, res) => {
const eventId = req.body.id;
if (processedEvents.has(eventId)) {
return res.status(200).json({ received: true, duplicate: true });
}
processedEvents.add(eventId);
// Process event...
res.status(200).json({ received: true });
});
3. Log Everything
app.post('/webhooks/accessgate', (req, res) => {
console.log({
event_id: req.body.id,
event_type: req.body.type,
timestamp: req.body.created_at,
entity_id: req.body.data?.entity_id
});
res.status(200).json({ received: true });
});
Testing Webhooks
Using ngrok for Local Development
# Terminal 1: Start your server
node server.js
# Terminal 2: Expose with ngrok
ngrok http 3000
Then configure the ngrok URL as your webhook endpoint:
curl -X POST "https://ag-staging.runloci.com/v1/webhooks/config" \
-H "x-org-id: your_org_id" \
-H "x-api-key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://abc123.ngrok.io/webhooks/accessgate",
"events": ["risk.high"],
"enabled": true
}'
Support
- Email: support@runloci.com
- Documentation: https://docs.runloci.com/accessgate