Webhooks
Overview
Webhooks let you receive real-time HTTP POST notifications when events happen in Cotizera. Instead of polling the API, register a URL and Cotizera will push event data to you automatically.
Webhooks are a PRO plan feature.
Available Events
| Event | Description |
|---|---|
quote.created |
A new quote was created |
quote.status_changed |
A quote's status transitioned (e.g., Generated → Sent → Won) |
quote.revised |
A new version of a quote was created (PRO) |
client.created |
A new client was added |
product.created |
A new product was added |
webhook.test |
Test event sent via the "Probar" (Test) button |
Payload Format
Every webhook delivery is a JSON POST with this structure:
{
"event": "quote.created",
"timestamp": "2026-03-31T14:30:00.000Z",
"data": {
"id": "clx1abc123",
"quoteNumber": "COT-0042",
"status": "GENERATED",
"total": 15000.00,
"clientId": "clx2def456",
"createdAt": "2026-03-31T14:30:00.000Z"
}
}The data field contains the full resource object that triggered the event.
HTTP Headers
Each delivery includes these headers:
| Header | Description |
|---|---|
Content-Type |
application/json |
X-Cotizera-Signature |
HMAC-SHA256 signature of the request body |
X-Cotizera-Event |
Event type (e.g., quote.created) |
Creating a Webhook
Via the API
curl -X POST https://cotizera.com/api/webhooks \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{
"url": "https://your-server.com/webhook",
"events": ["quote.created", "quote.status_changed"]
}'The response includes a secret field — save this for signature verification.
Via the UI
- Go to "Configuración" (Settings) → "Webhooks"
- Click "Nuevo webhook" (New webhook)
- Enter your endpoint URL
- Select the events you want to subscribe to
- Save — note the webhook secret displayed
Signature Verification
Every webhook delivery is signed with your webhook's secret using HMAC-SHA256. Always verify signatures to ensure the request came from Cotizera.
The signature is in the X-Cotizera-Signature header and is computed over the raw request body.
Node.js
const crypto = require("crypto");
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express example
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-cotizera-signature"];
if (!verifyWebhook(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(req.body);
console.log(`Received ${event.event}:`, event.data);
res.status(200).send("OK");
});Python
import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# Flask example
@app.route("/webhook", methods=["POST"])
def handle_webhook():
signature = request.headers.get("X-Cotizera-Signature")
if not verify_webhook(request.data, signature, WEBHOOK_SECRET):
return "Invalid signature", 401
event = request.json
print(f"Received {event['event']}: {event['data']}")
return "OK", 200Retry Logic
If your endpoint doesn't respond with a 2xx status code (or times out after 5 seconds), Cotizera retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
After 5 failed attempts, the delivery is marked as permanently failed and the tenant owner receives an in-app notification.
Testing Webhooks
Send a test event to any registered webhook:
curl -X POST https://cotizera.com/api/webhooks/{id}/test \
-b cookies.txtThis sends a webhook.test event with a sample payload to your endpoint. The response includes:
{
"success": true,
"statusCode": 200
}You can also click "Probar" (Test) on any webhook in the Cotizera UI.
Delivery Logs
View delivery history for a webhook:
curl https://cotizera.com/api/webhooks/{id}/deliveries \
-b cookies.txtEach delivery record includes the event, payload, HTTP status code, response body, and timestamp.
Best Practices
- Respond quickly — Return a
200within 5 seconds. Process the event asynchronously if needed. - Verify signatures — Always validate
X-Cotizera-Signatureto prevent spoofed requests. - Handle duplicates — In rare cases, events may be delivered more than once. Use the event
timestampand resourceidfor idempotency. - Use HTTPS — Always use an HTTPS endpoint to protect payload data in transit.
- Monitor delivery logs — Check the webhook delivery logs in the Cotizera UI to debug failures.