Receive changes as they happen
Subscribe an HTTPS endpoint to exact events or resource wildcards, then verify every delivery before processing it.
Create a subscription
Register a public HTTPS endpoint with the events it should receive. You can subscribe to exact names such as customer.created, resource wildcards such asinvoice.*, or * for all events.
curl --request POST \
--url "https://quick-goldfinch-475.eu-west-1.convex.site/api/v1/webhooks" \
--header "Authorization: Bearer $CRAFTIVO_API_KEY" \
--header "X-Organization-Id: $CRAFTIVO_ORGANIZATION_ID" \
--header "Idempotency-Key: webhook-setup-001" \
--header "Content-Type: application/json" \
--data '{
"url": "https://example.com/webhooks/craftivo",
"events": ["customer.created", "invoice.*"]
}'Delivery format
craftivo sends a JSON POST request. The delivery ID stays stable across retries, so it can also be used for deduplication.
Content-Type: application/json
X-Webhook-Event: customer.created
X-Webhook-Delivery: delivery_…
X-Webhook-Timestamp: 1786291200
X-Webhook-Signature: 7f1c…{
"id": "event_…",
"event": "customer.created",
"createdAt": 1786291200000,
"data": {
"customer": {
"id": "customer_…",
"name": "Müller Haustechnik GmbH"
}
}
}Verify the HMAC signature
Compute a hex HMAC-SHA256 over <timestamp>.<raw body>using the endpoint secret. Use the exact raw request body before JSON parsing, reject old timestamps, and compare equal-length buffers in constant time.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyCraftivoWebhook(input: {
rawBody: string;
timestamp: string;
signature: string;
secret: string;
}): boolean {
const { rawBody, timestamp, signature, secret } = input;
const timestampSeconds = Number(timestamp);
if (
!Number.isFinite(timestampSeconds) ||
Math.abs(Date.now() / 1000 - timestampSeconds) > 300
) {
return false;
}
const expected = Buffer.from(
createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex"),
"hex",
);
const received = Buffer.from(signature, "hex");
return (
received.length === expected.length &&
timingSafeEqual(received, expected)
);
}Retries and automatic disabling
Each request times out after 10 seconds. A non-2xx response, redirect, timeout, or network error is retried up to five times after 1 minute, 5 minutes, 15 minutes, 1 hour, and 6 hours.
After ten final delivery failures in a row, the endpoint is disabled. HTTP 410 disables it immediately. Disabled endpoints receive nothing until they are reactivated.