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
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"]
}'
The response includes a secret field that is shown only once. Store it immediately — you need it to verify webhook signatures.
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.
Every webhook delivery sends a JSON payload with this structure:
{
"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.
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.
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)
Always verify signatures before processing webhook payloads. This prevents attackers from sending fake events to your endpoint.
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:
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:
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 |