AutoUncle CoDriver

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's whsec_ 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 other 4xx they are not retried. 410 Gone switches 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 GET first: 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.

View this page as Markdown