Webhooks
Add an HTTPS endpoint under Settings → Developers → Webhook endpoints and AutoUncle CoDriver sends it an event every time something happens. Choose event types, or none to receive every type, including types added later.
Events
| Type | Fires when | subject |
|---|---|---|
lead.received |
A lead reached AutoUncle CoDriver (pushed, emailed or uploaded) | led_… |
lead.processed |
AutoUncle CoDriver read the lead and put it on an opportunity | led_… |
opportunity.created |
A new opportunity was opened | opp_… |
opportunity.updated |
Its details, vehicles, intents or handling state changed (data.change says which) |
opp_… |
opportunity.assigned |
A salesperson was assigned, reassigned or unassigned | opp_… |
opportunity.closed |
It was closed: won, lost, disqualified, no response, other or expired | opp_… |
opportunity.reopened |
A closed opportunity was opened again | opp_… |
message.received |
The customer wrote (email, SMS, WhatsApp…) | opp_… |
message.sent |
AutoUncle CoDriver or a salesperson wrote to the customer | opp_… |
appointment.requested |
The customer asked for a visit without a time | apt_… |
appointment.booked |
An appointment was booked | apt_… |
appointment.rescheduled |
A booked appointment's time or items changed | apt_… |
appointment.confirmed |
The dealer confirmed an appointment | apt_… |
appointment.cancelled |
An appointment was cancelled or rejected | apt_… |
Each event's data has a JSON Schema at https://codriver.autouncle.com/developers/schemas/v1/<type>.json, also linked from the event's dataschema.
The request
The body is a CloudEvent (structured JSON mode):
{
"specversion": "1.0",
"id": "evt_019a1b2c-3d4e-7f5a-8b6c-7d8e9f0a1b2c",
"source": "https://codriver.autouncle.com/organizations/org_019535d9-3df7-79fb-b466-fa907fa17f9e",
"type": "opportunity.closed",
"subject": "opp_019535d9-3df7-79fb-b466-fa907fa17f9e",
"time": "2026-10-23T09:41:00Z",
"datacontenttype": "application/json",
"dataschema": "https://codriver.autouncle.com/developers/schemas/v1/opportunity.closed.json",
"test": false,
"data": {
"opportunity": {
"id": "opp_019535d9-3df7-79fb-b466-fa907fa17f9e",
"status": "closed",
"closed_reason": "won",
"closed_at": "2026-10-23T09:41:00Z",
"handling_state": "in_progress",
"assigned_to": null,
"business_unit": null,
"customer": { "id": "cus_019535d9-3df7-79fb-b466-fa907fa17f9e", "name": "Anna Jensen", "email": "anna@example.com", "phone": null, "preferred_locale": "da" },
"vehicles": [],
"intents": [],
"external_references": [ { "system": "my-crm", "id": "4711" } ],
"created_at": "2026-10-20T08:00:00Z",
"updated_at": "2026-10-23T09:41:00Z",
"last_customer_activity_at": "2026-10-22T17:30:00Z",
"test": false
}
}
}
The headers follow Standard Webhooks:
webhook-id: the event ID. Delivery is at least once: dedupe on it.webhook-timestamp: Unix seconds.webhook-signature:v1,<base64 HMAC-SHA256 of "id.timestamp.body">keyed with your endpoint'swhsec_secret. During a secret rotation two signatures arrive, space-separated; accept either.
Verify every request
Use a Standard Webhooks library with the endpoint's signing secret (shown once when you add the endpoint or rotate it). Verify against the raw request body, before parsing it, and reject timestamps more than five minutes old (the libraries do).
// npm install standardwebhooks
import { Webhook } from "standardwebhooks";
const wh = new Webhook(process.env.CODRIVER_WEBHOOK_SECRET); // whsec_…
app.post("/codriver-webhook", express.raw({ type: "application/json" }), (req, res) => {
let event;
try {
event = wh.verify(req.body, req.headers); // throws unless AutoUncle CoDriver signed it
} catch {
return res.status(400).end();
}
// claimEvent records the ID and returns false if it was already there, in one atomic
// step (e.g. INSERT … ON CONFLICT DO NOTHING), so two deliveries of one event can't both pass.
if (claimEvent(event.id)) enqueue(event); // do the work later
res.status(204).end();
});
# pip install standardwebhooks
import os
from standardwebhooks.webhooks import Webhook
wh = Webhook(os.environ["CODRIVER_WEBHOOK_SECRET"]) # whsec_…
@app.post("/codriver-webhook")
def codriver_webhook():
try:
event = wh.verify(request.get_data(), dict(request.headers))
except Exception:
return "", 400
# claim_event records the ID atomically (e.g. INSERT … ON CONFLICT DO NOTHING) and
# returns False if it was already there, so two deliveries of one event can't both pass.
if claim_event(event["id"]):
enqueue(event)
return "", 204
Answer fast, and what happens when you don't
Answer 2xx within 15 seconds and do the work afterwards.
408,429,5xx, timeouts and connection errors are retried after 5 s, 5 min, 30 min, 2 h, 5 h, 10 h and 10 h.- Redirects (
3xx) are not followed, and like other4xxthey are not retried.410 Goneswitches the endpoint off. - Retries mean events can arrive out of order, and event IDs don't tell you the order. Use the ID only to dedupe. If you overwrite records with the snapshot, read the resource again with
GETfirst: it always returns the current state. - An endpoint that has accepted nothing for five days is switched off when an event finally fails; switch it on again under Settings → Developers.
- Missed something? Every event is also in the events feed for 30 days, and you can resend any delivery from the endpoint's page.
Deliveries go to https addresses that resolve to the public internet only.