Webhooks
Webhooks provide real-time notifications when events occur in your workspace. Instead of polling the API for changes, Colleckt delivers HTTP POST requests to your configured endpoints when verification lifecycle events fire.
Overview
Webhooks are useful for:
- Receiving notifications when a verification status changes
- Updating your internal systems when a decision is made
- Triggering downstream workflows (e.g., sending emails, updating databases)
- Logging verification activity in your own systems
How Webhooks Work
Key Concepts
| Concept | Description |
|---|---|
| Webhook | A registered endpoint URL that receives HTTP POST requests when events occur |
| Event | A verification lifecycle event that triggers delivery. You subscribe one or more events to each webhook |
| Signing Secret | A unique 64-character HMAC secret, auto-generated per webhook. Used to sign every payload so you can verify it came from Colleckt |
| Delivery | The actual HTTP POST request sent to your webhook URL. Includes the event payload, signature header, and any custom headers you configured |
Quickstart
Set up your first webhook in under 5 minutes.
1. Create a Webhook
Navigate to Webhooks in the workspace dashboard and click New Webhook.
| Field | Required | Description |
|---|---|---|
| URL | Yes | The endpoint that will receive webhook payloads. Use HTTPS in production |
| Events | Yes | One or more events to subscribe to. See Events |
| Active | No | Enable or disable the webhook without deleting it (default: enabled) |
| SSL Verification | No | Verify the target server's SSL certificate (default: enabled). |
| Custom Headers | No | Additional HTTP headers sent with every delivery |
| Authentication | No | Credentials sent in the Authorization header. This is useful for API key-based authentication on your endpoint. |
2. Save Your Signing Secret
After creation, the signing secret is displayed exactly once. Copy it immediately and store it in a secure location (secrets manager, environment variable, or vault).
WARNING
The signing secret is not exposed through the API or dashboard after creation. If lost, you must delete the webhook and create a new one.
3. Test Your Integration
- Use the sandbox environment to test webhook delivery before going live
- Check the Webhooks tab on the verification detail page to confirm delivery
- Verify the HMAC signature on every received payload (see Verifying Signatures)
Events
Colleckt fires webhook events at key points in a verification's lifecycle.
Actively Dispatched Events
The following events trigger a webhook delivery to all active endpoints that subscribe to them:
| Event | Code | Fires When |
|---|---|---|
| Verification Started | verification.started | A new verification is created and the end-user session begins |
| Verification Submitted | verification.submitted | The end-user submits all required documents for processing |
| Verification Finished | verification.finished | A verification reaches a terminal status — approved or rejected |
| Verification Canceled | verification.canceled | A verification is canceled before completion |
Verification Lifecycle
Webhooks fire at the four transition points above.
Double Check Status
When a verification enters a Double Check state (requires manual review), the verification.finished webhook is not sent. The webhook is only delivered when the verification reaches a terminal approved or rejected state.
Registered Events (Not Currently Dispatched)
The following event codes exist in the system and can be selected when configuring a webhook, but they are not currently dispatched as webhook deliveries:
| Event | Code | Status |
|---|---|---|
| Document Uploaded | document.uploaded | Registered, not dispatched |
| Document Canceled | document.canceled | Registered, not dispatched |
| Decision Made | decision.made | Registered, not dispatched |
| Decision Canceled | decision.canceled | Registered, not dispatched |
If you select these events, no webhook will be sent when they occur. This may change in a future release.
Managing Webhooks
View Webhooks
The webhooks list shows all configured webhooks with their URL, active status, subscribed events, and header count.
Update Webhook
You can modify the URL, events, authentication, custom headers, SSL verification, and active status.
The signing secret cannot be changed. If you need a new secret, delete the webhook and create a new one.
Delete Webhook
Deleting a webhook removes it permanently. Any undelivered events for this webhook are discarded.
Webhooks Delivery
Delivery Format
Each webhook delivery is an HTTP POST request:
| Header | Value |
|---|---|
Content-Type | application/json |
Signature | HMAC-SHA256 hex digest of the raw request body |
| Custom headers | Any headers you configured on the webhook |
Authorization | If authentication is configured on the webhook |
Payload
{
"event": "verification.finished",
"data": {
"id": "ver_xxxxx",
"url": "https://{workspace}.colleckt.io/spa/v3/.../process",
"qr_code": "https://{workspace}.colleckt.io/qr/...",
"flow": "usps-1583",
"expected_data": { /* ... */ },
"extracted_data": { /* ... */ },
"vendor_reference": "REF-001",
"vendor_state": "Wyoming",
"documents": [ /* ... */ ],
"comments": [ /* ... */ ],
"status_label": "Approved",
"created_at": "2026-06-14T02:14:13.000000Z",
"updated_at": "2026-06-14T02:16:42.000000Z"
}
}See Webhook Payloads for complete field descriptions and examples for every event type.
Retry Logic
If your endpoint does not return a 2xx status code within 10 seconds, delivery is retried:
| Property | Value |
|---|---|
| Maximum attempts | 3 |
| Timeout per attempt | 10 seconds |
| Backoff strategy | Exponential (delay increases between attempts) |
Webhook Logs
Every delivery attempt is recorded in the webhooks tab, accessible from the verification detail page.
From the tab you can:
- View details of the full request and response
- Resend a failed webhook from the stored payload
- Mark as solved to acknowledge investigation is complete
Manual Resends
Manual resends from the webhook logs are delivered without an HMAC signature and without SSL verification. The original signature cannot be recomputed from the stored log payload. Verify manual resends through endpoint authentication or sender IP validation.
Webhook Security
Signature Verification
Each webhook request includes a Signature header. You must verify this signature to confirm the request came from Colleckt and was not tampered with during transit.
Signature: {hex_encoded_hmac_sha256_signature}The signature is the lowercase hexadecimal output of:
HMAC-SHA256(raw_request_body, signing_secret)Getting the Raw Request Body
A common pitfall is parsing the JSON body and re-encoding it, which changes key order, whitespace, or encoding. Always use the raw, unparsed request body.
Verification Steps
- Get the raw request body as a string (do not parse and re-encode)
- Get the
Signatureheader value (64-character lowercase hex string) - Compute HMAC-SHA256 of the raw body using your webhook's signing secret
- Convert the HMAC output to hexadecimal (lowercase)
- Compare with the
Signatureheader using a constant-time comparison
Code Examples
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signatureHeader, signingSecret) {
const expected = crypto
.createHmac('sha256', signingSecret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signatureHeader, 'hex'),
Buffer.from(expected, 'hex')
);
}function verifyWebhookSignature(string $rawBody, string $signatureHeader, string $signingSecret): bool {
$expected = hash_hmac('sha256', $rawBody, $signingSecret);
return hash_equals($expected, $signatureHeader);
}import hmac
import hashlib
def verify_webhook_signature(raw_body: str, signature_header: str, signing_secret: str) -> bool:
expected = hmac.new(
signing_secret.encode('utf-8'),
raw_body.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
func VerifyWebhookSignature(rawBody, signatureHeader, signingSecret string) bool {
h := hmac.New(sha256.New, []byte(signingSecret))
h.Write([]byte(rawBody))
expected := hex.EncodeToString(h.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signatureHeader))
}require 'openssl'
def verify_webhook_signature(raw_body, signature_header, signing_secret)
expected = OpenSSL::HMAC.hexdigest(
'SHA256',
signing_secret,
raw_body
)
ActiveSupport::SecurityUtils.secure_compare(expected, signature_header)
endCommon Pitfalls
- Use the raw body bytes, not the parsed JSON object. Re-serializing JSON changes whitespace, key order, and encoding — all of which break the signature. Capture the raw body before any JSON parsing.
- Use constant-time comparison (
hash_equals,hmac.compare_digest,timingSafeEqual). Never use==— naive comparison leaks bytes through timing side channels. - Treat the signing secret as a credential. Never put it in source code, never log it, never include it in error responses.
- Validate before processing. If the signature does not match, return
401 Unauthorizedand do not process the payload.
Test Vector
Verify your signature implementation using a known payload:
Secret: abc123
Payload: {"event":"verification.started","data":{"id":"ver_test"}}
Signature: 6f8603bad35dd38facbb351d53c5d666ed36a01b6da270d998f883f527932b33Compute the HMAC-SHA256 hex digest of the payload string using your secret. The result must match the signature above. Changing any character in the payload or secret produces a completely different hash.
Express.js Full Example
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use('/webhooks', express.raw({ type: 'application/json' }));
app.post('/webhooks', (req, res) => {
const rawBody = req.body.toString();
const signature = req.headers['signature'];
const signingSecret = process.env.COLLECKT_WEBHOOK_SECRET;
if (!verifyWebhookSignature(rawBody, signature, signingSecret)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(rawBody);
console.log('Received event:', event.event);
res.status(200).send('OK');
});SSL Verification
Each webhook has a verify_ssl flag (default: true). When enabled, Colleckt validates the target server's SSL certificate on every delivery.
| Setting | Behavior |
|---|---|
true (default) | Validates the SSL certificate. Rejects delivery if the certificate is invalid, self-signed, or expired |
false | Skips SSL validation. Intended for testing with self-signed certificates only |
Security Risk
Disabling SSL verification exposes your integration to man-in-the-middle attacks. Only disable for testing.
Best Practices
- Respond quickly — Return a 2xx status within the 10-second timeout. Defer processing to a background job if needed
- Handle duplicates — Delivery is at-least-once. You may receive the same event multiple times. Use idempotency keys or event deduplication
- Verify signatures — Always verify the
Signatureheader. This is the only way to confirm the request came from Colleckt - Process asynchronously — Don't perform long-running operations in your webhook handler. Enqueue work for later processing
- Log all deliveries — Keep a record of webhook events for debugging and audit purposes
- Use the sandbox — Test your webhook integration in the sandbox environment before going live
- Store the signing secret securely — It is shown only once at creation. Use a secrets manager (AWS Secrets Manager, Vault, 1Password)
- Monitor failed deliveries — Configure email subscriptions for failed webhooks and check logs regularly
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Endpoint unreachable | Network error, DNS failure, or firewall | Verify the URL is accessible from the public internet |
| Invalid signature | Wrong secret, re-encoded body, wrong encoding | Use raw request body, HMAC hex output, and the correct signing secret |
| SSL verification failed | Invalid, self-signed, or expired certificate | Use a valid SSL certificate, or disable verification for testing only |
| Timeout | Endpoint takes longer than 10 seconds | Optimize your endpoint or delegate work to a background job |
| 4xx/5xx response | Application error on your endpoint | Check your endpoint logs for the error |
| Webhook not received | Inactive webhook, wrong URL, event not selected | Verify the webhook is active, the URL is correct, and events are subscribed |
No verification.finished event | Verification is in Double Check status | Await manual review. The finished event fires after a human approves or rejects |