# 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)

```javascript
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/](https://github.com/hookdeck/webhook-skills/tree/main/skills/socure-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/socure-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/socure-webhooks/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_execution_failed` | 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_notification_monitored_search_updated` | A monitored entity was added to or removed from a watchlist |
| `case_management_notification_case_closure` | A watchlist case was approved or closed from the RiskOS Dashboard |

## Environment Variables

```bash
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):

```bash
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](https://github.com/hookdeck/webhook-skills/blob/main/skills/socure-webhooks/references/overview.md) - What Socure RiskOS webhooks are, events and payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/socure-webhooks/references/setup.md) - Create the webhook in Developer Workbench with Basic auth
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/socure-webhooks/references/verification.md) - Basic auth check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/socure-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests