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

# Create Webhook Destination

> Create a webhook destination. The response includes the auto-generated `secret_shared_key` HMAC signing secret (also readable later via `GET /v3/webhook/destinations/{destination_uuid}/`). Always send `subscribed_events` with at least one event (no wildcards): an explicit empty list is rejected with 400, and omitting the field creates a destination subscribed to nothing, which never receives a webhook. The `(application, url)` pair must be unique among non-deleted destinations.

export const AgentPromptAccordion = ({prompt, title = "AI Agent Integration Prompt"}) => {
  const [copied, setCopied] = React.useState(false);
  const handleCopy = e => {
    e.stopPropagation();
    if (!prompt) return;
    navigator.clipboard.writeText(prompt.trim()).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };
  const agents = ["Claude Code", "Codex", "Cursor", "Devin", "Windsurf", "GitHub Copilot"];
  return <div className="didit-agent-card">
      {}
      <div className="didit-agent-titlebar">
        <div className="didit-agent-dots" aria-hidden="true">
          <span className="didit-agent-dot didit-agent-dot-red"></span>
          <span className="didit-agent-dot didit-agent-dot-yellow"></span>
          <span className="didit-agent-dot didit-agent-dot-green"></span>
        </div>
        <span className="didit-agent-filename">{title}</span>
        <button type="button" className={`didit-agent-copy ${copied ? "didit-agent-copy-copied" : ""}`} onClick={handleCopy} title="Copy prompt to clipboard" aria-label={copied ? "Copied!" : "Copy prompt to clipboard"}>
          {copied ? <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <path d="M3 8.5l3.5 3.5L13 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
              <span>Copied</span>
            </> : <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5" />
                <path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" stroke="currentColor" strokeWidth="1.5" />
              </svg>
              <span>Copy</span>
            </>}
        </button>
      </div>

      {}
      <pre className="didit-agent-body"><code>{prompt.trim()}</code></pre>

      {}
      <div className="didit-agent-footer">
        <span className="didit-agent-footer-label">Paste into</span>
        <div className="didit-agent-chips">
          {agents.map(name => <span key={name} className="didit-agent-chip">{name}</span>)}
        </div>
      </div>
    </div>;
};

<AgentPromptAccordion
  title="Create Webhook Destination API Prompt"
  prompt={`Register a new webhook destination for my Didit application through the Management API.

Endpoint:
POST https://verification.didit.me/v3/webhook/destinations/

Authentication:
Use the x-api-key header with my Didit API key.

Goal:
- Create a destination where Didit pushes the events I subscribe to. Each destination is an independent (URL, signing secret, payload version, event subscription) tuple.

Request body (application/json):
{
"label": "Production session webhooks",        // required, human-readable
"url": "https://yourapp.com/webhooks/didit",   // required, HTTPS URL; must be unique per application among non-deleted destinations
"enabled": true,                                 // optional, default true
"webhook_version": "v3",                        // optional, "v1" | "v2" | "v3" — v3 is current/recommended
"subscribed_events": ["status.updated", "data.updated"]  // required, at least one supported event
}

Supported subscribed_events values (no wildcard):
- status.updated, data.updated — KYC/KYB session lifecycle.
- user.status.updated, user.data.updated — User entity changes.
- business.status.updated, business.data.updated — Business entity changes.
- activity.created — Activity timeline records (console / manual review actions).
- transaction.created, transaction.status.updated — Transaction monitoring events.

Critical: capture secret_shared_key from the response and store it securely.
- The response includes secret_shared_key — this is the HMAC signing secret used to compute X-Signature, X-Signature-V2, and X-Signature-Simple headers on every webhook delivered to this URL.
- Per the audited OpenAPI spec, treat the create response as your one chance to capture this secret. (GET will continue to return it for keys with read:webhooks, but storing on create avoids accidental log leaks of follow-up reads.)
- The secret is unique per-destination.

Example call:
curl -X POST "https://verification.didit.me/v3/webhook/destinations/" \\
-H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \\
-d '{
"label": "Production session webhooks",
"url": "https://yourapp.com/webhooks/didit",
"webhook_version": "v3",
"subscribed_events": ["status.updated", "data.updated"]
}'

Signature verification quick-start (HMAC-SHA256 over the raw request body):
import hmac, hashlib
def verify(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)

Always verify against the raw request body, not the parsed JSON. See /integration/webhooks for the full v3 signature scheme.

Failure modes:
- 400 — duplicate URL among non-deleted destinations, empty subscribed_events, unknown event name, or invalid URL.
- 401 — missing or malformed x-api-key.
- 403 — canonical { "detail": "You do not have permission to perform this action." } envelope.

Side effects:
- A new destination is created and will start receiving deliveries for subscribed_events immediately (assuming enabled: true).
- Not idempotent — repeated POSTs with different metadata create separate destinations.

For the full integration shape, see /integration/integration-prompt.`}
/>

Webhook destinations let you send different Didit events to different URLs. Each destination has its own `secret_shared_key`, `webhook_version`, and `subscribed_events` list.

## How `subscribed_events` works

`subscribed_events` is a required array of exact event names. Didit delivers a webhook to this destination only when the payload's `webhook_type` matches one of the values in the array.

* Include at least one event.
* Use only the supported event names below.
* Add every event you want delivered; there is no wildcard value like `*` or `all`.
* To route different product areas to different systems, create multiple destinations with different `subscribed_events` lists.

## Event catalog

| Event                        | Subscribe when you need                                                                                                                                   |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status.updated`             | User Verification (KYC) or Business Verification (KYB) session status changes. KYB payloads include `session_kind: "business"` and `business_session_id`. |
| `data.updated`               | Session verification data is corrected or updated after creation, such as reviewer edits to KYC, Proof of Address, or KYB data.                           |
| `user.status.updated`        | A User entity changes status, for example `ACTIVE`, `FLAGGED`, or `BLOCKED`.                                                                              |
| `user.data.updated`          | A User entity profile, counters, metadata, documents, or aggregate verification fields change.                                                            |
| `business.status.updated`    | A Business entity changes status, for example after manual review, KYB results, or a blocklist action.                                                    |
| `business.data.updated`      | A Business entity profile, counters, metadata, registration fields, or aggregate verification fields change.                                              |
| `activity.created`           | A timeline activity is recorded for a User, Business, external counterparty, session, or transaction.                                                     |
| `transaction.created`        | A Transaction Monitoring transaction is created and its initial rule evaluation is complete.                                                              |
| `transaction.status.updated` | A transaction changes status after rules, analyst review, remediation, provider updates, or API/console actions.                                          |

Common subscription sets:

```json theme={null}
{
  "subscribed_events": ["status.updated", "data.updated"]
}
```

```json theme={null}
{
  "subscribed_events": ["transaction.created", "transaction.status.updated"]
}
```

```json theme={null}
{
  "subscribed_events": [
    "user.status.updated",
    "user.data.updated",
    "business.status.updated",
    "business.data.updated",
    "activity.created"
  ]
}
```

For payload examples and signing code, see the [webhooks guide](/integration/webhooks).


## OpenAPI

````yaml POST /v3/webhook/destinations/
openapi: 3.0.0
info:
  version: 3.0.0
  title: Didit Verification API
  description: Identity verification API. Authenticate with x-api-key header.
servers:
  - url: https://verification.didit.me
security: []
tags: []
paths:
  /v3/webhook/destinations/:
    post:
      tags:
        - Webhook
      summary: Create webhook destination
      description: >-
        Create a webhook destination. The response includes the auto-generated
        `secret_shared_key` HMAC signing secret (also readable later via `GET
        /v3/webhook/destinations/{destination_uuid}/`). Always send
        `subscribed_events` with at least one event (no wildcards): an explicit
        empty list is rejected with 400, and omitting the field creates a
        destination subscribed to nothing, which never receives a webhook. The
        `(application, url)` pair must be unique among non-deleted destinations.
      operationId: create_webhook_destination
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - label
                - url
              properties:
                label:
                  type: string
                  maxLength: 255
                  description: >-
                    Human-readable name displayed in Console → Webhooks. Helps
                    distinguish destinations when you have several (e.g. "Prod",
                    "Staging mirror").
                url:
                  type: string
                  format: uri
                  description: >-
                    HTTPS URL that will receive `POST` requests. Must be unique
                    per application among non-deleted destinations. Use a
                    stable, hardened endpoint — Didit retries failed deliveries
                    on a backoff schedule.
                enabled:
                  type: boolean
                  default: true
                  description: >-
                    When `false`, the destination is saved but Didit will not
                    push any webhooks to it. Useful for staging configs you want
                    to keep parked.
                webhook_version:
                  type: string
                  enum:
                    - v1
                    - v2
                    - v3
                  description: >-
                    Payload schema version Didit will send. `v3` is the
                    current/recommended format; older versions are kept for
                    backward compatibility with legacy integrations.
                subscribed_events:
                  allOf:
                    - $ref: '#/components/schemas/WebhookSubscribedEvents'
                  description: >-
                    Events to deliver. Effectively required: an explicit `[]` is
                    rejected with 400, and omitting the field creates a
                    destination with no subscriptions that receives nothing.
            examples:
              session_updates:
                summary: Session status and data updates
                value:
                  label: Production session webhooks
                  url: https://yourapp.com/webhooks/didit
                  webhook_version: v3
                  subscribed_events:
                    - status.updated
                    - data.updated
              transaction_updates:
                summary: Transaction monitoring updates
                value:
                  label: Transaction monitoring webhooks
                  url: https://yourapp.com/webhooks/didit-transactions
                  webhook_version: v3
                  subscribed_events:
                    - transaction.created
                    - transaction.status.updated
              entity_updates:
                summary: User and Business entity updates
                value:
                  label: Entity lifecycle webhooks
                  url: https://yourapp.com/webhooks/didit-entities
                  webhook_version: v3
                  subscribed_events:
                    - user.status.updated
                    - user.data.updated
                    - business.status.updated
                    - business.data.updated
                    - activity.created
      responses:
        '201':
          description: >-
            Destination created. `secret_shared_key` is the HMAC signing secret
            — store it now.
          content:
            application/json:
              schema:
                type: object
                properties:
                  uuid:
                    type: string
                    format: uuid
                    description: >-
                      Stable destination identifier. Use as `{destination_uuid}`
                      in subsequent calls.
                  label:
                    type: string
                  url:
                    type: string
                    format: uri
                  enabled:
                    type: boolean
                  webhook_version:
                    type: string
                    enum:
                      - v1
                      - v2
                      - v3
                  secret_shared_key:
                    type: string
                    description: >-
                      Auto-generated HMAC signing secret bound to this
                      destination (43-character URL-safe token). Use it to
                      verify the signature headers on incoming webhooks. Always
                      returned in full to API-key callers; Console users need
                      the `read:webhooks` permission, otherwise they see an
                      empty string.
                  subscribed_events:
                    $ref: '#/components/schemas/WebhookSubscribedEvents'
                  created_at:
                    type: string
                    format: date-time
                  updated_at:
                    type: string
                    format: date-time
                  summary:
                    type: object
                    description: >-
                      Delivery health summary (all zero/null on a brand-new
                      destination).
                    properties:
                      total_deliveries:
                        type: integer
                      failed_deliveries:
                        type: integer
                      error_rate_percentage:
                        type: integer
                      min_response_time_ms:
                        type: integer
                        nullable: true
                      avg_response_time_ms:
                        type: integer
                        nullable: true
                      max_response_time_ms:
                        type: integer
                        nullable: true
                      last_delivery_at:
                        type: string
                        format: date-time
                        nullable: true
              examples:
                Created:
                  value:
                    uuid: 11111111-2222-3333-4444-555555555555
                    label: Production session webhooks
                    url: https://yourapp.com/webhooks/didit
                    enabled: true
                    webhook_version: v3
                    secret_shared_key: tno2NTeRC1ZK3sBOYlBJDyEzBVENl3pQ1AcZyAW0xnQ
                    subscribed_events:
                      - status.updated
                      - data.updated
                    created_at: '2026-05-17T10:00:00Z'
                    updated_at: '2026-05-17T10:00:00Z'
                    summary:
                      total_deliveries: 0
                      failed_deliveries: 0
                      error_rate_percentage: 0
                      min_response_time_ms: null
                      avg_response_time_ms: null
                      max_response_time_ms: null
                      last_delivery_at: null
        '400':
          description: >-
            Validation failed — usually a duplicate URL, missing/invalid
            `subscribed_events`, or unknown event name.
          content:
            application/json:
              examples:
                Duplicate URL:
                  value:
                    url:
                      - >-
                        This webhook destination already exists for the
                        application.
                Empty events list:
                  value:
                    subscribed_events:
                      - At least one subscribed event is required.
                Unknown event:
                  value:
                    subscribed_events:
                      - 'Unsupported webhook events: foo.bar'
        '403':
          description: >-
            API key missing, malformed, expired, or scoped to a different
            application. Didit returns 403 (never 401) for all authentication
            failures on this endpoint.
          content:
            application/json:
              examples:
                Forbidden:
                  value:
                    detail: You do not have permission to perform this action.
        '429':
          description: Rate limit exceeded; back off and retry.
          content:
            application/json:
              examples:
                Throttled:
                  value:
                    detail: Request was throttled. Expected available in 30 seconds.
      security:
        - ApiKeyAuth: []
      x-codeSamples:
        - lang: curl
          label: curl
          source: >-
            curl -X POST
            'https://verification.didit.me/v3/webhook/destinations/' \
              -H 'x-api-key: YOUR_API_KEY' \
              -H 'Content-Type: application/json' \
              -d '{
                "label": "Production session webhooks",
                "url": "https://yourapp.com/webhooks/didit",
                "webhook_version": "v3",
                "subscribed_events": ["status.updated", "data.updated"]
              }'
        - lang: python
          label: Python
          source: |-
            import os

            import requests

            resp = requests.post(
                'https://verification.didit.me/v3/webhook/destinations/',
                headers={
                    'x-api-key': os.environ['DIDIT_API_KEY'],
                    'Content-Type': 'application/json',
                },
                json={
                    'label': 'Production session webhooks',
                    'url': 'https://yourapp.com/webhooks/didit',
                    'webhook_version': 'v3',
                    'subscribed_events': ['status.updated', 'data.updated'],
                },
                timeout=10,
            )
            resp.raise_for_status()
            destination = resp.json()
            # Store secret_shared_key in your secret manager.
            print('Destination UUID:', destination['uuid'])
            print('Signing secret:', destination['secret_shared_key'])
        - lang: javascript
          label: JavaScript
          source: >-
            const resp = await
            fetch('https://verification.didit.me/v3/webhook/destinations/', {
              method: 'POST',
              headers: {
                'x-api-key': process.env.DIDIT_API_KEY,
                'Content-Type': 'application/json',
              },
              body: JSON.stringify({
                label: 'Production session webhooks',
                url: 'https://yourapp.com/webhooks/didit',
                webhook_version: 'v3',
                subscribed_events: ['status.updated', 'data.updated'],
              }),
            });

            if (!resp.ok) throw new Error(await resp.text());

            const destination = await resp.json();

            console.log('Store this secret securely:',
            destination.secret_shared_key);
components:
  schemas:
    WebhookSubscribedEvents:
      type: array
      description: >-
        Event filter for this webhook destination. Didit delivers only webhooks
        whose event type exactly matches one of these values — there is no
        wildcard subscription. When sent, the list must contain at least one
        valid event (an explicit `[]` is rejected with 400); a destination whose
        list is empty (field omitted at create) receives nothing. On update the
        list is replaced wholesale, never merged.
      minItems: 1
      uniqueItems: true
      items:
        $ref: '#/components/schemas/WebhookEvent'
      example:
        - status.updated
        - data.updated
    WebhookEvent:
      type: string
      description: >-
        Supported webhook event name. Use these exact strings in
        `subscribed_events`; unsupported values are rejected.
      enum:
        - status.updated
        - data.updated
        - user.status.updated
        - user.data.updated
        - business.status.updated
        - business.data.updated
        - activity.created
        - transaction.created
        - transaction.status.updated
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

````