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

# Create Webhook

> Create a webhook endpoint.

<span data-api-safety-label="changes-state"><Badge color="yellow" size="sm" shape="pill">Changes state - verify before retry</Badge></span>

<Info>
  **Recommended.** This endpoint is part of the public `/v1` API reference and is the supported path for new integrations. See the [migration guide](/docs/api-reference/v1/migrating-to-v1) for versioning guidance.
</Info>

Registers a URL endpoint to receive event notifications. When a call ends, DialNexa sends an HTTP `POST` to your URL with a signed plain JSON payload. DialNexa does not encrypt the webhook body.

The `secret` is required. Generate a long random value, store it in a secret manager, and send it only over HTTPS. The create response is the only response that exposes the unmasked value; later reads mask it.

## Payload delivered when a call ends

```json theme={null}
{
  "call_id": "mrjurp82fXXDJr",
  "status": "completed",
  "sentiment": "positive",
  "summary": "The agent confirmed the appointment.",
  "transcript": "...",
  "extracted_data": { "name": "John", "intent": "book_appointment" },
  "duration": 120,
  "recording_url": "https://...",
  "agent_id": "agent_FBbwBB8Lof7Mtm",
  "metadata": { "call_id": null },
  "billing": {
    "total_amount_inr": 5.5,
    "billed_duration_seconds": 120,
    "billing_plan": { "id": "abc", "name": "Standard" },
    "line_items": [{ "item_type": "call", "rate_inr": 0.05, "units": 120, "amount_inr": 5.5 }]
  }
}
```

<Note>`billing` is only present for organizations on the new billing system.</Note>

## Verifying the signature

Every delivery includes an `x-nexa-signature` header:

```
x-nexa-signature: sha256=<hex>
```

Computed as `HMAC-SHA256(secret, rawBody)` over the **raw request body**. Always verify against the raw body before parsing JSON.

```js Node.js theme={null}
const crypto = require('crypto');

function verifySignature(rawBody, secret, signatureHeader) {
  if (!signatureHeader || !signatureHeader.startsWith('sha256=')) {
    return false;
  }

  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  const expectedBuffer = Buffer.from(expected);
  const receivedBuffer = Buffer.from(signatureHeader);

  return expectedBuffer.length === receivedBuffer.length &&
    crypto.timingSafeEqual(expectedBuffer, receivedBuffer);
}
```

```python Python theme={null}
import hmac, hashlib

def verify_signature(raw_body: bytes, secret: str, signature_header: str) -> bool:
    signature_header = signature_header or ''
    if not signature_header.startswith('sha256='):
        return False
    expected = 'sha256=' + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header or '')
```

<Note>
  Some older webhook docs and legacy delivery paths use `x-dialnexa-signature`. For this `/v1/user-webhooks` call-ended delivery path, verify `x-nexa-signature`.
</Note>

## When to use this

* **Call outcome processing**: get notified the moment a call ends instead of polling [Get Call Details](/docs/api-reference/v1/calls/get), then write the outcome to your CRM or trigger the next step.
* **Live dashboards**: track call starts and completions in real time.

## Available events

| Event            | Triggered when                    |
| ---------------- | --------------------------------- |
| `call.completed` | A call completes successfully.    |
| `call.failed`    | A call reaches a failure outcome. |

Use only event strings accepted by the request schema. Event availability can vary as the catalog evolves; do not translate dots to underscores.


## OpenAPI

````yaml POST /v1/user-webhooks
openapi: 3.0.0
info:
  title: DialNexa API
  description: >-
    Public REST API for the DialNexa voice AI platform. Versioned endpoints are
    under /v1; unversioned endpoints are legacy and deprecated.
  version: 1.0.0
servers:
  - url: https://api.dialnexa.com
    description: DialNexa production API
security:
  - bearer: []
tags:
  - name: API Keys
  - name: Agent Functions
  - name: Agents V1
  - name: Agents2
  - name: Batch Calls
  - name: Batch Calls V1
  - name: Call Logs
  - name: Calls
  - name: Calls V1
  - name: Campaign Leads
  - name: Campaigns
  - name: External Webhooks
  - name: Knowledge Base
  - name: Knowledge Base V1
  - name: LLMs
  - name: LLMs V1
  - name: Languages
  - name: Languages V1
  - name: Organization Phone Numbers
  - name: Organization Phone Numbers V1
  - name: Phone Number Pricing
  - name: Phone Number Pricing V1
  - name: Transcribers
  - name: Transcribers V1
  - name: User Webhooks
  - name: User Webhooks V1
  - name: V1
  - name: Voices
  - name: Voices V1
  - name: Webcall
  - name: Workflow Leads
  - name: Workflow Leads V1
  - name: Workflows
  - name: Workflows V1
paths:
  /v1/user-webhooks:
    post:
      tags:
        - User Webhooks
        - User Webhooks V1
        - V1
      summary: Create Webhook
      description: >-
        Registers a new webhook URL for your organization. The secret is
        returned only once at creation - store it securely. For v1 call-ended
        deliveries, DialNexa sends a plain JSON body and an x-nexa-signature
        header in the format sha256=<hex> computed with HMAC-SHA256 over the raw
        request body.
      operationId: UserWebhooksV1Controller_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserWebhookDto'
            examples:
              request:
                summary: Create a webhook
                value:
                  url: https://example.com/dialnexa/webhook
                  events:
                    - call.completed
                  is_active: true
                  secret: replace-with-a-long-random-secret
      responses:
        '201':
          description: Webhook created successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Webhook created successfully
                  webhook:
                    type: object
                    properties:
                      id:
                        type: string
                        example: webhook_abc123
                      url:
                        type: string
                        example: https://webhook.site/your-endpoint
                      events:
                        type: array
                        items:
                          type: string
                        example:
                          - call.completed
                          - call.failed
                      is_active:
                        type: boolean
                        example: true
                      secret:
                        type: string
                        example: mySuperSecret
                        description: Shown once at creation only
                      createdAt:
                        type: string
                        format: date-time
              examples:
                success:
                  summary: Successful response
                  value:
                    id: webhook_abc123
                    url: https://example.com/dialnexa/webhook
                    events:
                      - call.completed
                    is_active: true
                    createdAt: '2026-07-03T10:30:00.000Z'
        '400':
          description: Bad request - missing required fields or invalid URL.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                error:
                  summary: 400 Bad Request
                  value:
                    statusCode: 400
                    message: url must be a valid URL
                    error: Bad Request
        '401':
          description: Unauthorized - missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                error:
                  summary: 401 Unauthorized
                  value:
                    statusCode: 401
                    message: API key is missing or invalid
                    error: Unauthorized
        '403':
          description: Forbidden - API key does not have access to this organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                error:
                  summary: 403 Forbidden
                  value:
                    statusCode: 403
                    message: You do not have permission to access this resource
                    error: Forbidden
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                error:
                  summary: 500 Internal Server Error
                  value:
                    statusCode: 500
                    message: Internal server error
                    error: Internal Server Error
      security:
        - bearer: []
components:
  schemas:
    CreateUserWebhookDto:
      type: object
      properties:
        url:
          type: string
          example: https://webhook.site/your-endpoint
          description: The URL to which webhook events will be sent.
        events:
          example:
            - order.paid
            - order.failed
          description: List of events this webhook is subscribed to.
          type: array
          items:
            type: string
        is_active:
          type: boolean
          example: true
          description: Whether the webhook is active.
        secret:
          type: string
          example: mySuperSecret
          description: Secret used to sign webhook payloads.
      required:
        - url
        - events
        - secret
    ErrorResponse:
      type: object
      properties:
        statusCode:
          type: integer
          example: 400
        message:
          oneOf:
            - type: string
            - type: array
              items:
                type: string
          example: phone_number must be a valid E.164 phone number
        error:
          type: string
          example: Bad Request
      required:
        - statusCode
        - message
        - error
  securitySchemes:
    bearer:
      scheme: bearer
      bearerFormat: JWT
      type: http

````