Concepts
Webhooks
Signed JSON events Covo sends to your URL (and to Zapier, Make or n8n), how to verify them, and the booking webhooks Covo receives.
Set webhooks up in Studio: Settings, Integrations, Webhook. Covo generates a signing secret and shows it once.
Events
lead.createdlead.qualifiedlead.stage_changedbooking.createdagent.configuration.updatedagent.version.createdagent.version.restoredagent.draft.appliedagent.test.completedagent.publishedagent.unpublished
What Covo sends
POST https://your-endpoint.example/hook
Content-Type: application/json
User-Agent: Covo-Webhooks/1.0
X-Covo-Event: lead.created
X-Covo-Delivery: 7d0f...
X-Covo-Timestamp: 1791403629
X-Covo-Signature: sha256=5c2b...
{
"id": "7d0f...",
"event": "lead.created",
"createdAt": "2026-10-07T18:20:29.000Z",
"tenant": { "id": "...", "slug": "avery", "name": "Avery Quinn" },
"data": { "lead": { "id": "...", "name": "Rae Banks", "email": "rae@banks.example", "stage": "lead", "score": 42 } }
}The same headers are also sent with the older X-Concierge- prefix, for receivers built before the rename. id (and X-Covo-Delivery) stays the same across retries: use it to ignore duplicates.
Verify the signature
X-Covo-Signature is sha256= followed by the hex HMAC-SHA256 of {timestamp}.{raw body}, keyed with your secret. Compute it over the raw body (before parsing), compare in constant time, and reject timestamps more than 5 minutes old.
import crypto from 'node:crypto';
export function verifyCovo(rawBody, headers, secret) {
const ts = headers['x-covo-timestamp'];
const sig = headers['x-covo-signature'] ?? '';
if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex');
return sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}import hashlib, hmac, time
def verify_covo(raw_body: bytes, headers: dict, secret: str) -> bool:
ts = headers.get("x-covo-timestamp", "")
sig = headers.get("x-covo-signature", "")
if not ts or abs(time.time() - int(ts)) > 300:
return False
expected = "sha256=" + hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(sig, expected)Zapier, Make and n8n
- Zapier: use the "Webhooks by Zapier" trigger, Catch Hook (a paid Zapier feature). To check signatures, add a Code by Zapier step with the Node.js check above.
- Make: add a Custom webhook module. Check the signature with Make's
sha256function using your secret as the key, or a Code module. - n8n: add a Webhook node (POST), copy the Production URL and activate the workflow (the test URL only works while you are testing). Check the signature with the Crypto node (HMAC, SHA256, hex).
Retries
A delivery that fails with a network error, a timeout, 408, 429 or 5xx is retried up to 6 times over about an hour. Other 4xx answers mean the address or setup is wrong and are not retried. Studio shows every attempt in the integration's delivery history.
Booking webhooks Covo receives
Cal.com and Calendly tell Covo about bookings, so the person's lead moves to Opportunity (or is created).
- Cal.com: Covo shows a webhook URL and secret. In Cal.com open Settings, Developer, Webhooks, New; paste both; turn on Booking created, rescheduled and cancelled (and requested, if bookings need confirmation); keep the default payload. Covo checks
x-cal-signature-256(HMAC-SHA256 of the raw body). - Calendly: needs a paid Calendly plan. Paste a Calendly personal access token in Studio and Covo creates the webhook subscription for you (Calendly has no screen for it), signed with a key Covo generates. Covo checks
Calendly-Webhook-Signature(HMAC-SHA256 oft.body, 5 minute tolerance). - The same signed delivery is accepted once; repeats are acknowledged and ignored.