Agent skill
Socure Webhooks Skill
Receive and authenticate Socure RiskOS webhooks. Use when setting up a Socure webhook endpoint, validating the HTTP Basic Authorization header, debugging 401s, or handling events like evaluation_completed, evaluation_paused, decision_update and case_status_updated.
Install this skill
npx skills add hookdeck/webhook-skills --skill socure-webhooks
Socure RiskOS sends a JSON webhook when something happens to an evaluation or a case: an evaluation completes or pauses, a reviewer changes a decision, a case is assigned or gets notes. Webhooks are not signed. Each webhook is created with an auth type (Basic, Bearer or OAuth 2.0) that can't be changed afterwards, and RiskOS includes those credentials on every request. This skill covers Basic: your endpoint authenticates the request by comparing the Authorization: Basic base64(username:password) header with the credentials you set.
When to Use This Skill
- How do I receive Socure RiskOS webhooks?
- How do I authenticate Socure webhooks with Basic auth?
- Does Socure sign webhook payloads? (No: credentials are sent on every request)
- How do I handle
evaluation_completed,decision_updateorcase_status_updatedevents? - Why does my Socure webhook endpoint return 401?
Verification (core)
const crypto = require('crypto');
function safeEqual(a, b) { // equal-length digests: timingSafeEqual never throws
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifySocureRequest(headers, user = process.env.SOCURE_WEBHOOK_USERNAME,
pass = process.env.SOCURE_WEBHOOK_PASSWORD) {
if (!user || !pass) return false; // never accept when unconfigured
const m = /^basic\s+([A-Za-z0-9+/]+={0,2})\s*$/i.exec(headers.authorization || '');
if (!m) return false;
const decoded = Buffer.from(m[1], 'base64').toString('utf8');
const i = decoded.indexOf(':'); // split on the FIRST colon
if (i < 0) return false;
const userOk = safeEqual(decoded.slice(0, i), user);
const passOk = safeEqual(decoded.slice(i + 1), pass);
return userOk && passOk; // evaluate both, no short-circuit
}
- Header:
Authorization: Basic base64(username:password); the scheme is case-insensitive. - Compare username and password each in constant time and evaluate both before deciding. In Python use
hmac.compare_digeston bytes (encode both sides;strraises on non-ASCII). - Return
401for a missing or malformed header or any mismatch,400for a body that isn't a JSON object,200once handled. - Read the body raw (
express.raw({ type: '*/*' })) and authenticate before parsing it. - The credentials must arrive on the first request. Socure's docs don't say whether Basic credentials are sent preemptively, so don't rely on a
401+WWW-Authenticatechallenge flow: it won't work through Hookdeck, which verifies the request as received. - Security: a static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS only, deduplicate on
event_id, and rotate the credential if it leaks. RiskOS can't edit credentials on an existing webhook: delete it and create a new one.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Types
The payload is an envelope: event_id (UUID, use for idempotency), event_at, event_type and data (varies by event). Available types depend on your account entitlements.
| Event | Sent When |
|---|---|
evaluation_completed | An evaluation finished processing with a final decision |
evaluation_paused | An evaluation paused waiting for async input (for example DocV, OTP) |
reevaluation | A case was reevaluated and finished processing |
workflow_ | An evaluation failed to execute |
decision_update | A reviewer changed the decision on a case |
fraud_confirming | A case was marked as fraud or non-fraud |
review_case_assigned | A case was assigned to a reviewer |
review_case_unassigned | A case was unassigned from a reviewer |
case_status_updated | A case status or sub-status changed |
case_notes_added | Notes were added to a case |
case_attachment_added | Attachments were added to a case |
watchlist_ | A monitored entity was added to or removed from a watchlist |
case_ | A watchlist case was approved or closed from the RiskOS Dashboard |
Environment Variables
SOCURE_WEBHOOK_USERNAME=your-webhook-username # Basic auth username set on the webhook
SOCURE_WEBHOOK_PASSWORD=your-webhook-password # Basic auth password set on the webhook
Local Development
Socure can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 socure --path /webhooks/socure
Use the printed URL as the webhook URL in the RiskOS Dashboard.
Resources
- references/overview.md - What Socure RiskOS webhooks are, events and payload shape
- references/setup.md - Create the webhook in Developer Workbench with Basic auth
- references/verification.md - Basic auth check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests