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

# Webhooks

> Receive real-time HTTP notifications when proposals are created, shared, accepted, or when leads arrive in Quikly.

Webhooks send HTTP POST requests to your server whenever specific events occur in Quikly. Use them to sync with your CRM, trigger n8n workflows, send Slack notifications, or build custom automations.

## Create a webhook

You can create webhooks from the Quikly UI or through the API.

### From the UI

Go to **Settings → Webhooks** and click **Create Webhook**. Enter a name, your endpoint URL, and select the events you want to subscribe to.

### From the API

```bash theme={null}
curl -X POST "https://api.getquikly.com/api/external/v1/webhooks" \
  -H "X-API-Key: qk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "CRM sync",
    "url": "https://your-server.com/webhooks/quikly",
    "events": ["proposal.created", "proposal.shared", "proposal.accepted"]
  }'
```

<Warning>
  The response includes a `secret` field that is shown **only once**. Store it immediately — you need it to verify webhook signatures.
</Warning>

## Available events

| Event                         | Fires when                                                            |
| ----------------------------- | --------------------------------------------------------------------- |
| `proposal.created`            | A new proposal is created                                             |
| `proposal.updated`            | A proposal is modified (requirements, settings, pricing)              |
| `proposal.shared`             | A proposal is shared with a client via link                           |
| `proposal.accepted`           | A client accepts a proposal                                           |
| `proposal.rejected`           | A client rejects a proposal                                           |
| `proposal.revision_requested` | A client requests changes to a proposal                               |
| `lead_session.created`        | A new lead session starts (from widget, Telegram, or API)             |
| `lead_session.updated`        | A lead session status changes (e.g., gathering info → proposal ready) |

You can subscribe to specific events or listen to all of them by including each event in the `events` array.

## Payload format

Every webhook delivery sends a JSON payload with this structure:

```json theme={null}
{
  "event": "proposal.accepted",
  "timestamp": "2026-04-03T14:30:00Z",
  "webhook_id": "wh_abc123",
  "data": {
    "proposal_id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Cloud Migration - Acme Corp",
    "status": "accepted",
    "client_name": "John Smith",
    "total": 24500.00,
    "currency": "USD",
    "share_url": "https://app.getquikly.com/p/abc123token",
    "responded_at": "2026-04-03T14:30:00Z"
  }
}
```

For `lead_session` events, the `data` object contains session fields like `channel`, `channel_id`, `client_name`, `brief`, and `status`.

## HTTP headers

Each webhook request includes these headers:

| Header                | Description                                 |
| --------------------- | ------------------------------------------- |
| `X-Quikly-Event`      | Event type (e.g., `proposal.accepted`)      |
| `X-Quikly-Signature`  | HMAC-SHA256 hex digest of the request body  |
| `X-Quikly-Timestamp`  | Unix timestamp of when the webhook was sent |
| `X-Quikly-Webhook-Id` | ID of the webhook configuration             |
| `Content-Type`        | Always `application/json`                   |

## Verify signatures

Every webhook is signed with the secret you received when creating it. Verify the `X-Quikly-Signature` header to confirm the request came from Quikly.

<CodeGroup>
  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_webhook(payload_body: bytes, secret: str, signature: str) -> bool:
      expected = hmac.new(
          secret.encode("utf-8"),
          payload_body,
          hashlib.sha256,
      ).hexdigest()
      return hmac.compare_digest(expected, signature)
  ```

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

  function verifyWebhook(payloadBody, secret, signature) {
    const expected = crypto
      .createHmac("sha256", secret)
      .update(payloadBody, "utf-8")
      .digest("hex");
    return crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signature)
    );
  }
  ```
</CodeGroup>

<Tip>
  Always verify signatures before processing webhook payloads. This prevents attackers from sending fake events to your endpoint.
</Tip>

## Retry policy

If your endpoint returns a non-2xx status code or times out (10-second limit), Quikly retries the delivery with exponential backoff:

| Attempt   | Delay      |
| --------- | ---------- |
| 1st retry | 30 seconds |
| 2nd retry | 2 minutes  |
| 3rd retry | 15 minutes |
| 4th retry | 1 hour     |
| 5th retry | 4 hours    |

After 5 failed retries, the delivery is marked as failed. You can see all attempts in the webhook logs.

## Test a webhook

Send a test event to verify your endpoint is reachable:

```bash theme={null}
curl -X POST "https://api.getquikly.com/api/external/v1/webhooks/{webhook_id}/test" \
  -H "X-API-Key: qk_your_api_key_here"
```

The test payload uses the event type `test` and contains sample data. Your endpoint should return a 200 status code.

## View delivery logs

Check recent deliveries and debug failures:

```bash theme={null}
curl "https://api.getquikly.com/api/external/v1/webhooks/{webhook_id}/logs" \
  -H "X-API-Key: qk_your_api_key_here"
```

Each log entry includes the HTTP status code, response body, response time, and the full payload that was sent.

You can also view logs in the Quikly UI under **Settings → Webhooks → (select webhook) → Logs**.

## Managing webhooks

| Operation            | API endpoint                                      | UI path                    |
| -------------------- | ------------------------------------------------- | -------------------------- |
| List all webhooks    | `GET /webhooks`                                   | Settings → Webhooks        |
| Update events or URL | `PUT /webhooks/{id}`                              | Click webhook → Edit       |
| Pause deliveries     | `PUT /webhooks/{id}` with `is_active: false`      | Toggle switch              |
| Delete               | `DELETE /webhooks/{id}`                           | Click webhook → Delete     |
| Regenerate secret    | `POST /webhooks/{id}/regenerate-secret` (UI only) | Click webhook → Regenerate |
