> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cr3dentials.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive verification results in real time instead of polling.

Webhooks notify your endpoint the moment a verification session reaches a terminal status. Configure a webhook once and you stop polling for results.

## How it works

<Steps>
  <Step title="Configure">
    Add a webhook URL in the Partner Portal. Deliveries cover every session created by your API key.
  </Step>

  <Step title="Verify">
    A user completes (or fails) their verification session.
  </Step>

  <Step title="Deliver">
    Cr3dentials sends a `POST` to your URL when the session reaches a terminal status: `COMPLETED`, `PARTIAL_COMPLETE`, `ERROR`, `CANCELLED`, or `TERMINATED`.
  </Step>

  <Step title="Process">
    Your endpoint receives the payload and updates your application.
  </Step>
</Steps>

## Create a webhook in the Partner Portal

<Steps>
  <Step title="Open Webhooks">
    Log into the [Partner Portal](https://app.cr3dentials.xyz) and click **Webhooks** in the sidebar.
  </Step>

  <Step title="Create a webhook">
    Click **Create Webhook** and enter your endpoint URL (`https://your-domain.com/webhook`). It must be HTTPS.
  </Step>

  <Step title="Set authentication (optional)">
    Add a custom header key/value (for example `X-API-Key: your-secret`). Cr3dentials includes it on every delivery so you can validate the request server-side.
  </Step>

  <Step title="Tune delivery (optional)">
    Set the response timeout and retry attempts, then toggle the webhook **Active**.
  </Step>
</Steps>

<Tip>
  You can create a webhook inactive and enable it later. Use the **Test** action in the
  portal to send a sample payload and confirm your endpoint responds.
</Tip>

## Payload

Each delivery is a `POST` with this body:

```json theme={null}
{
  "sessionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "COMPLETED",
  "platformId": 1,
  "externalReferenceId": "user_abc123",
  "extractedData": {
    "accountHolder": "John Doe",
    "accountType": "checking"
  },
  "proofHash": "9f2c…",
  "completedAt": "2025-03-24T12:05:00.000Z",
  "metadata": {}
}
```

<ResponseField name="sessionId" type="string">
  The session this event is for.
</ResponseField>

<ResponseField name="status" type="string">
  Terminal status: `COMPLETED`, `PARTIAL_COMPLETE`, `ERROR`, `CANCELLED`, or `TERMINATED`.
</ResponseField>

<ResponseField name="platformId" type="number">
  Platform the session verified against.
</ResponseField>

<ResponseField name="externalReferenceId" type="string | null">
  Your reference ID from session creation, if you provided one.
</ResponseField>

<ResponseField name="extractedData" type="object | null">
  Verified account data, present when verification completed.
</ResponseField>

<ResponseField name="proofHash" type="string | null">
  Opaque reference to the cryptographic attestation (SHA-256). The raw attestation is never sent.
</ResponseField>

<ResponseField name="completedAt" type="string | null">
  ISO 8601 completion timestamp.
</ResponseField>

<ResponseField name="metadata" type="object | null">
  Non-sensitive session metadata. Internal fields are stripped before delivery.
</ResponseField>

## Handle the webhook

```javascript theme={null}
// Node.js / Express
app.post('/webhook/cr3dentials', (req, res) => {
  // Validate the auth header you configured in the portal
  if (req.headers['x-api-key'] !== process.env.CR3D_WEBHOOK_SECRET) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Acknowledge immediately, process asynchronously
  res.status(200).json({ received: true });

  const { sessionId, status, extractedData } = req.body;
  setImmediate(() => updateVerification(sessionId, status, extractedData));
});
```

## Requirements

Your endpoint must:

<Check>Respond with a `2xx` status code.</Check>
<Check>Respond within the configured timeout (default 30 seconds).</Check>
<Check>Handle duplicate deliveries idempotently (key off `sessionId`).</Check>

<Warning>
  Always validate the configured authentication header server-side before trusting a
  payload. Acknowledge fast and do heavy work asynchronously so you don't hit the timeout.
</Warning>

## Retries

Failed deliveries are retried automatically with exponential backoff, up to the retry count configured for the webhook (default 3). Review delivery history and failures under **Webhooks → Logs** in the portal.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Not receiving notifications">
    Confirm the webhook is **Active**, the URL is reachable over HTTPS with a valid certificate, and your server returns a `2xx`. Check the delivery logs in the portal.
  </Accordion>

  <Accordion title="Deliveries failing">
    Common causes: non-2xx responses, slow responses past the timeout, or TLS certificate problems. Inspect the logged status code and error for each attempt.
  </Accordion>

  <Accordion title="Auth header mismatch">
    Make sure the header key and value in your endpoint exactly match what you configured in the portal.
  </Accordion>
</AccordionGroup>
