Guide to Socure Webhooks: Features and Best Practices
Socure webhooks tell your systems the outcome of identity verification and fraud checks. When a RiskOS evaluation finishes with a decision, pauses for a document or one-time passcode, or fails to run, and when a reviewer changes a decision, marks fraud or updates a case, RiskOS can notify your application so onboarding, account access or manual review moves on without polling.
This guide covers how RiskOS webhooks are configured and authenticated, the events and the envelope they share, the retry rules, and the practices that keep a Socure receiver correct and secure.
What are Socure webhooks?
RiskOS is Socure's platform for running identity and fraud workflows. A RiskOS webhook belongs to one use case, such as consumer onboarding, and subscribes to one or more event types. When a subscribed event happens, RiskOS sends a JSON POST to your HTTPS endpoint, wrapped in a standard envelope with an event_id, event_at, event_type and a data object.
RiskOS does not sign webhooks. Each webhook is created with one of three authentication methods, and RiskOS includes the credentials in every request: Basic authentication (a username and password in an Authorization: Basic header), a Bearer token (a UUID in Authorization: Bearer), or OAuth 2.0 (RiskOS fetches a fresh token from your token URL before each delivery). This guide focuses on Basic authentication, which is what Hookdeck's Socure source type checks.
Socure webhook features
| Feature | Details |
|---|---|
| Configuration | RiskOS Dashboard > Developer Workbench > Webhooks > Add a webhook, or the RiskOS CLI |
| Fields | Name, URL, use case, event types, auth type, status (ACTIVE or INACTIVE) |
| Endpoint | HTTPS with TLS 1.2 or higher, accepting POST and application/json |
| Authentication | Basic, Bearer (UUID) or OAuth 2.0 client credentials, chosen at creation |
| Changing credentials | Not possible. Delete and recreate the webhook to rotate or switch |
| Signature and timestamp | None. event_at is in the body but is not signed |
| Envelope | event_id, event_at, event_type, data |
| Response | 2xx within 30 seconds |
| Retries | Up to 10 attempts, exponential backoff capped at 60 seconds, attempts 6 to 10 a minute apart |
| Retried status codes | 504, 503, 502, 429, 423, 409, 408 |
| Duplicates | Possible, even for successfully delivered events |
| Custom headers | Static header variables, set up through your Solutions Consultant |
| Source IPs | Published for sandbox and production |
| Testing | Continue to Test sends a sample payload before you save |
Common events
| Group | Event types | Sent when |
|---|---|---|
| Evaluation | evaluation_completed, evaluation_paused, reevaluation, workflow_ | An evaluation finishes with a final decision, pauses for async input such as DocV or OTP, is reevaluated, or fails to execute |
| Case management | decision_update, fraud_confirming, review_case_assigned, review_case_unassigned, case_status_updated, case_notes_added, case_attachment_added | A reviewer changes a decision, a case is marked as fraud or non-fraud, assigned or unassigned, changes status, or gets notes or attachments |
| Watchlist | watchlist_, case_ | A monitored entity is added to or removed from a watchlist, or a watchlist case is approved or closed |
Which events you can select depends on your account's entitlements. Here is Socure's case_status_updated example, trimmed:
{
"event_id": "82ef8e08-24b4-42c6-9e27-f7b9aa892c58",
"event_at": "2023-09-14T07:46:55.956104Z",
"event_type": "case_status_updated",
"data": {
"id": "abc-test-123",
"workflow": "consumer_onboarding",
"eval_id": "d3a15e54-ed6b-43f5-8fea-8f6370cc89d9",
"reviewer_id": "test2@abc.com",
"decision": "REVIEW",
"status": "OPEN",
"sub_status": "Awaiting Documentation",
"updated_at": "2023-09-14T07:46:55.956018Z",
"environment_name": "Sandbox"
}
}
data.id is your own identifier for the evaluation and data.eval_id is RiskOS's. For evaluation events, data follows the same schema as the Evaluation API response, including decision and any data_enrichments, so it can contain sensitive personal data.
Inspect Socure webhooks as they arrive. Point a sandbox webhook at a Hookdeck Inspect URL and use Continue to Test to see the envelope and headers, with no account or setup required. Keep real customer data out of any third-party test tool.
Setting up Socure webhooks
Choose a username and a long random password first, for example with openssl rand -hex 32 for the password. You cannot change them later without recreating the webhook, so store them in your secrets manager before you start.
- In the RiskOS Dashboard, go to Developer Workbench > Webhooks and click Add a webhook.
- Enter a name and your HTTPS endpoint URL.
- Choose the Use Case the webhook applies to.
- Select the event types you need.
- Set Auth Type to Basic Authentication and enter the username and password.
- Click Continue to Test to send a sample payload, then Save to activate the webhook.
If you restrict inbound traffic by IP, allowlist Socure's addresses before testing, or the test can fail. Note that the two Socure pages that list them disagree: the set-up guide lists three addresses each for sandbox and production, while the configuration reference lists four for sandbox and five for production, as CIDR ranges. Socure says it notifies customers before changing them, so confirm the current list with Socure Support.
To route through Hookdeck, create a Hookdeck source with the Socure type and use its URL in step 2. The source asks for a Username and Password: enter the same pair. It accepts POST.
The Hookdeck source checks HTTP Basic credentials only, so a webhook created with a Bearer token or OAuth 2.0 will not verify. Hookdeck also never answers with a 401 challenge, so RiskOS has to send the credentials with the first request. Socure's docs say RiskOS includes the configured credentials in every outbound request, but do not describe the exchange in more detail, so check the test delivery in Hookdeck to confirm it arrived with an Authorization: Basic header.
Securing Socure webhooks
Decode the Authorization: Basic header, split on the first colon, and compare the username and password with your stored values in constant time.
const crypto = require('crypto');
const express = require('express');
const USERNAME = process.env.SOCURE_WEBHOOK_USERNAME;
const PASSWORD = process.env.SOCURE_WEBHOOK_PASSWORD;
function safeEqual(a, b) {
// timingSafeEqual throws on unequal lengths, so check first.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
function verifyBasicAuth(header) {
if (!USERNAME || !PASSWORD || typeof header !== 'string') return false;
const match = /^basic\s+([A-Za-z0-9+/]+={0,2})\s*$/i.exec(header);
if (!match) return false;
const decoded = Buffer.from(match[1], 'base64');
const colon = decoded.indexOf(0x3a); // split on the first colon
if (colon === -1) return false;
const userOk = safeEqual(decoded.subarray(0, colon), Buffer.from(USERNAME, 'utf8'));
const passOk = safeEqual(decoded.subarray(colon + 1), Buffer.from(PASSWORD, 'utf8'));
return userOk && passOk;
}
const app = express();
app.post('/webhooks/socure', express.raw({ type: '*/*' }), (req, res) => {
if (!verifyBasicAuth(req.get('authorization'))) {
return res.sendStatus(401);
}
let event;
try {
event = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
// Deduplicate on event.event_id, then route on event.event_type.
res.sendStatus(200);
});
The same check in Python:
import base64
import binascii
import hmac
import os
USERNAME = os.environ.get("SOCURE_WEBHOOK_USERNAME", "")
PASSWORD = os.environ.get("SOCURE_WEBHOOK_PASSWORD", "")
def verify_basic_auth(header) -> bool:
if not USERNAME or not PASSWORD or not header:
return False
scheme, _, encoded = header.partition(" ")
if scheme.lower() != "basic":
return False
try:
decoded = base64.b64decode(encoded.strip(), validate=True)
except (binascii.Error, ValueError):
return False
user, sep, password = decoded.partition(b":")
if not sep:
return False
user_ok = hmac.compare_digest(user, USERNAME.encode("utf-8"))
pass_ok = hmac.compare_digest(password, PASSWORD.encode("utf-8"))
return user_ok and pass_ok
Basic credentials are static. They authenticate the sender but not the body, so a modified payload still passes, and event_at is not signed, so a captured request can be replayed. Use HTTPS, which RiskOS requires, a long random password, and allowlist Socure's IPs as a second layer. Treat payloads as untrusted, as Socure recommends, and store only the data you need.
Make Socure webhooks production-ready. Hookdeck Event Gateway checks the Basic credentials you configure, responds inside RiskOS's 30-second window, and keeps every evaluation and case event so none is lost to an outage.
Socure webhook limitations and pain points
Credentials are fixed for the life of the webhook
The Problem: Rotating a leaked password, or switching from Basic to another method, means deleting the webhook and creating a new one.
Why It Happens: RiskOS does not let you change a webhook's auth type, credentials or custom headers after creation. Editing only updates the URL and event subscriptions.
Workarounds:
- Create the replacement webhook first with the new credentials, accept both sets in your receiver briefly, then delete the old webhook.
- Keep a record of every webhook's use case and events so you can recreate it exactly.
How Hookdeck Can Help: Point every RiskOS webhook at a Hookdeck source. Rotation then means creating a new source and webhook, while your own service's URL and configuration stay the same behind Hookdeck.
Only some failures are retried
The Problem: A 500 from a bug, or a 401 from a credentials mismatch, is not retried, and the event is gone.
Why It Happens: RiskOS retries only 504, 503, 502, 429, 423, 409 and 408. Other errors end delivery for that event.
Workarounds:
- Return
503rather than500for transient failures in your own code. - Reconcile against the RiskOS API for evaluations you expected to hear about.
How Hookdeck Can Help: Hookdeck stores each request and delivers to your service with retries on your schedule, whatever your service returns. With Basic credentials configured, Hookdeck answers RiskOS with a 200 either way, records requests without matching credentials as failed verification, and never forwards them.
Only Basic fits the source type
The Problem: A webhook created with a Bearer token or OAuth 2.0 never verifies in Hookdeck's Socure source.
Why It Happens: RiskOS supports three auth types, and Hookdeck's Socure source type checks HTTP Basic credentials only. The auth type cannot be changed after creation.
Workarounds:
- Create the webhook with Basic Authentication if you plan to use Hookdeck's Socure source.
- If you are committed to Bearer or OAuth 2.0, check the token in your own receiver.
How Hookdeck Can Help: With Basic Authentication, the Socure source compares the username and password for every request, so your service only receives authenticated events.
Duplicates and sensitive payloads
The Problem: The same event can arrive more than once, and every copy may carry personal data.
Why It Happens: RiskOS retries, and Socure warns that it may retry successfully delivered events in rare edge cases. Evaluation events carry the full Evaluation API response.
Workarounds:
- Deduplicate on
event_idbefore processing. - Store minimal data from the webhook and fetch details from the RiskOS API when needed, as Socure recommends.
How Hookdeck Can Help: Hookdeck deduplication can drop repeated deliveries by event_id before they reach your service.
Best practices
Deduplicate on event_id
Socure designates event_id as the idempotency key. Store it before you act on an event, and skip anything you have already seen. See our guide to webhook idempotency.
Order by event_at
A decision_update may arrive before the evaluation_completed it follows. Compare event_at, or data.updated_at where present, with what you have stored before changing a decision.
Log delivery metadata
Record event_id, event_type and event_at for every request, as Socure recommends, so you can trace a decision back to the event that changed it.
Acknowledge fast and process asynchronously
Respond with 200 well inside 30 seconds and handle the event from a queue. See why to process webhooks asynchronously.
Use separate webhooks for sandbox and production
The environments have different source IPs and should have different credentials. data.environment_name tells you which one sent an event.
Conclusion
Socure RiskOS sends unsigned webhooks in a standard event_id, event_at, event_type, data envelope, authenticated with credentials fixed when the webhook is created. Use Basic Authentication, compare the decoded username and password in constant time over HTTPS, allowlist Socure's IPs, deduplicate on event_id, and respond within 30 seconds, remembering that only a handful of status codes are retried.
Hookdeck Event Gateway checks the Basic credentials, keeps every RiskOS event, and retries and replays delivery to your service, so a bug or an outage on your side does not drop an identity decision.
Get started with Hookdeck for free and handle Socure webhooks reliably in minutes.