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_update or case_status_updated events?
  • 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_digest on bytes (encode both sides; str raises on non-ASCII).
  • Return 401 for a missing or malformed header or any mismatch, 400 for a body that isn't a JSON object, 200 once 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-Authenticate challenge 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.

EventSent When
evaluation_completedAn evaluation finished processing with a final decision
evaluation_pausedAn evaluation paused waiting for async input (for example DocV, OTP)
reevaluationA case was reevaluated and finished processing
workflow_execution_failedAn evaluation failed to execute
decision_updateA reviewer changed the decision on a case
fraud_confirmingA case was marked as fraud or non-fraud
review_case_assignedA case was assigned to a reviewer
review_case_unassignedA case was unassigned from a reviewer
case_status_updatedA case status or sub-status changed
case_notes_addedNotes were added to a case
case_attachment_addedAttachments were added to a case
watchlist_notification_monitored_search_updatedA monitored entity was added to or removed from a watchlist
case_management_notification_case_closureA 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 4, 2026

View on GitHub →