Webhooks and the events feed

Get every new call, booking request, urgent message and missed call in your own system the moment it happens: as a signed webhook, or by polling the events feed.

Julius Answer sends events (a summarized call, a booking request, an urgent message, a missed call and more) to your https address as signed JSON, in order, with retries. The same events are available from GET /api/v1/events for systems that poll. Both use the same shape.

Connect a webhook

  • In the console: Settings, Connections, Webhook. Paste your https address, choose the events and press Connect. Send a test from the same screen.
  • With the API: PUT /api/v1/connections/webhook with {"url": "https://…"} (full access). Then POST /api/v1/connections/webhook/test sends a sample.
  • The signing secret (whsec_…) is shown once, when the webhook is first connected. Store it with your endpoint.
  • Addresses must be https on the public internet; private and local network addresses are refused.

Event types

Events Julius Answer sends
TypeWhen
call.summarizedA call ended and its summary is ready
message.urgentA caller left an urgent message
booking.requestedA caller asked for an appointment or a visit
call.missedA call the receptionist couldn't take (the reason is in text); a third try from the same caller sends another
transfer.missedA caller asked for a person and nobody picked up
caller.unhappyA caller sounded unhappy
promise.dueSomething the business promised a caller is coming due or slipping
household.suggestedTwo callers look like the same household; confirm in the console
call_list.readyThe morning's list of people worth a call today is ready
text.receivedA caller texted your Julius Answer number (texting must be on). Clinics and law firms get it without the text

The payload

POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: Julius-Webhooks/1
X-Julius-Event: booking.requested
X-Julius-Delivery: ea8f0f0d-74c5-46ee-95cf-fd05a6d8c6fb
X-Julius-Timestamp: 1792505805
X-Julius-Signature: sha256=5d1f…

{"id": "ea8f0f0d-74c5-46ee-95cf-fd05a6d8c6fb", "type": "booking.requested",
 "occurred_at": "2026-10-20T14:16:45.776822+00:00", "business": "Joe Plumbing",
 "caller": {"name": "Maria Lopez", "phone": "+13125550142"},
 "title": "Booking request: Maria Lopez", "text": "Water heater install, Saturday morning",
 "summary": "Water heater install, Saturday morning", "caller_type": null,
 "call_id": null, "link": "https://app.julius.dealsflow.ai"}
  • id is the same on every retry: use it to ignore duplicates.
  • call_id (when the event is about a call) works with GET /api/v1/calls/{call_id} for the transcript.
  • caller_type on call.summarized is customer, offer (a sales pitch) or another kind of caller.
  • Healthcare and legal businesses: caller names and reasons are left out ("Open Julius to read it") unless the owner turns on "Include caller details" for the webhook.

Check the signature

The signature is an HMAC-SHA256, with your signing secret, of the timestamp, a dot and the raw body. Compare it in constant time and refuse old timestamps.

# Python
import hashlib, hmac, time

def verified(secret: str, body: bytes, timestamp: str, signature: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:
        return False
    digest = hmac.new(secret.encode(), timestamp.encode() + b"." + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest("sha256=" + digest, signature)
// Node.js (use the raw body, before any JSON parsing)
import crypto from "node:crypto";

function verified(secret, rawBody, timestamp, signature) {
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(`${timestamp}.`).update(rawBody).digest("hex");
  return expected.length === signature.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Delivery

  • Answer 2xx quickly (within 10 seconds), then do the work. Anything else counts as a failure.
  • Retries wait longer each time, up to an hour apart, and events stay in order: a later event never overtakes one that is being retried.
  • Turned off at once on 404 or 410, after five refusals in a row (400, 401, 403), or after 50 failures. The console shows the reason; saving the webhook again turns it back on.
  • No old floods: events older than a day aren't replayed, and a new webhook starts with what happens next.
  • Redirects aren't followed. Use the final address.

The events feed (polling)

The same events, oldest first, for systems that can't receive webhooks. Read access is enough.

curl "https://app.julius.dealsflow.ai/api/v1/events?since_hours=24" -H "Authorization: Bearer $KEY"
{"events": [ … ], "next": "eyJhdCI6IjIwMjYtMTAtMjBUMTQ6…", "more": false}
  • Store next and pass it as after next time: you get only what's new, never the same event twice. next stays the same when nothing happened.
  • more: true means there's another page right away; ask again with the new next.
  • Filters: types=call.summarized,booking.requested; limit up to 100; since_hours up to 168 (only for the first request, without after).
  • Freshness: events appear about 15 seconds after they happen, so a cursor never skips one that was still being written.

Frequently asked questions

Can I send Julius Answer calls to Zapier or Make?

Yes. Make a catch-hook URL in Zapier (Webhooks by Zapier) or Make (custom webhook), and connect it in the console under Settings, Connections, Webhook. Every new call and booking arrives there as JSON.

Do webhooks include the transcript?

They include the call summary, the caller's name and number, a short headline and a link to the call in the console. Fetch the full transcript with GET /api/v1/calls/{call_id}.

What if my endpoint is down?

Julius Answer retries with growing waits (up to an hour apart) and delivers in order once it's back, for up to a day. An answer of 404 or 410 turns the webhook off at once; the console shows why.

Should I use webhooks or the events feed?

Webhooks if you can receive https requests: they arrive within seconds. The events feed if you can't (a script on a schedule, a spreadsheet, a tool without a public address): poll it every minute or so with the next value.