Developer docs

Connect ansame to your own app

Two ways in. A webhook pushes every finished call to your app as it happens, with the caller's details and a draft quote. The read API lets your app fetch calls and quotes whenever it likes. Each business has its own webhook, secret and keys, and can only ever see its own data.

Getting started

  1. Log in to the business's dashboard and open Developers.
  2. Paste your endpoint URL (it must start with https://) and save. ansame creates a signing secret for you.
  3. Press Send test. You'll see the delivery, the status code your server gave and its reply in the log.
  4. For the read API, press Create API key. The key is shown once, so copy it somewhere safe.

Webhooks

After every call, ansame sends a POST with a JSON body to your URL. Answer with any 2xx status within 8 seconds. Anything else counts as a failure and we try again after 1, 5, 30, 120 and 360 minutes (six tries in all). Every try shows in the delivery log, and you can retry any delivery by hand.

HeaderWhat it holds
X-Ansame-Eventcall.completed, or test for the Send test button
X-Ansame-DeliveryA unique id for this delivery. Retries keep the same id, so you can ignore repeats.
X-Ansame-TimestampUnix time in seconds when this try was sent
X-Ansame-Signaturesha256= followed by the HMAC-SHA256 of timestamp + "." + body, keyed with your signing secret

Checking signatures

Always check the signature before trusting a delivery, and turn away anything older than five minutes. In Node:

import crypto from "node:crypto";

export function verifyAnsame(rawBody, headers, secret) {
  const ts = headers["x-ansame-timestamp"];
  const sent = (headers["x-ansame-signature"] || "").replace("sha256=", "");
  const want = crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  return fresh && sent.length === want.length &&
    crypto.timingSafeEqual(Buffer.from(sent), Buffer.from(want));
}

Use the raw request body exactly as it arrived. Parsing and re-encoding the JSON changes the bytes and the check will fail.

The payload

{
  "event": "call.completed",
  "delivery_id": "dlv_mh2x…",
  "created_at": "2026-10-29T08:01:12.000Z",
  "business": { "id": "business_mvrwrk6r…", "name": "Demo Doors Ltd" },
  "call": {
    "id": "c0ffee12-…",
    "at": "2026-10-29T07:58:40.000Z",
    "duration_seconds": 142,
    "caller_name": "Mark Ellis",
    "caller_number": "+447700900418",
    "callback_number": "07700 900418",
    "caller_type": "New customer",
    "reason": "Wants a quote for a new front door and back door lock",
    "job_details": "Two composite front doors, back door lock replaced",
    "job_address": "14 Ferry Road, Gosport",
    "job_postcode": "PO12 1AB",
    "urgency": "normal",
    "best_time": "After 5pm",
    "notes": "",
    "summary": "Mark rang about replacing two front doors…",
    "unanswered_questions": []
  },
  "quote": { "id": "qt_…", "ref": "Q-2026-0001", "lines": [ … ], "subtotal": 3020, "vat": 604, "total": 3624, … }
}

quote is null when the call wasn't a job enquiry (care homes never get quotes).

Read API

Base address: https://ansame.uk/api/v1. Send your key in the Authorization header. Everything is read-only and returns JSON.

curl https://ansame.uk/api/v1/quotes?status=draft \
  -H "Authorization: Bearer hk_live_…"
EndpointReturns
GET /businessThe business the key belongs to
GET /callsCalls and messages, newest first. Options: limit (1–200, default 50), since (an ISO date)
GET /quotesQuotes, newest first. Options: limit, since, status (draft, sent, won, lost)
GET /quotes/{id}One quote with all its lines

Quote object

FieldMeaning
refHuman reference, e.g. Q-2026-0001
statusdraft, sent, won or lost
customername, phone, email, address, postcode
job, notesWhat the caller wants, and anything to check
lines[]description, qty, unit, unit_price, total, source (why it was added)
subtotal, vat_rate, vat, totalIn pounds. vat_rate is 0.2 for VAT-registered businesses, otherwise 0
wordingThe "rough estimate, to be confirmed" text shown on the quote

Bookings and calendars

Each business can let Sophie book appointments by phone (Dashboard → Bookings → Let Sophie book appointments directly; off by default). It can only be switched on once the business has set the hours it takes appointments. Sophie checks the diary, offers two or three free times, reads the chosen one back, and books it only after the caller says yes. She never books over an existing booking (the database refuses a second booking for a full slot, even when two calls try at the same moment). If anything goes wrong she takes a message instead.

Webhook events

EventWhen
call.completedAs before, now with a bookings array: every booking made on that call
booking.createdA booking was confirmed (by phone or in the dashboard)
booking.requestedCalendly couldn't take it automatically; link is the Calendly page for that time, to send the caller
booking.cancelledThe business cancelled it in the dashboard
{
  "event": "booking.created",
  "business": { "id": "business_…", "name": "Demo Doors Ltd" },
  "booking": {
    "id": "bk_…", "status": "confirmed", "type": "Quote visit",
    "start": "2026-11-03T13:30:00.000Z", "end": "2026-11-03T14:30:00.000Z",
    "when": "Tuesday 3rd November at 1:30pm",
    "name": "Sarah Collins", "phone": "07700 900512", "email": "", "notes": "…",
    "source": "phone", "provider": "builtin", "call_id": "…", "link": ""
  }
}

Times are UTC in start/end; when is UK time in words. Status is confirmed, requested or cancelled.

Calendar feed

Every business has a private .ics link (Bookings → See bookings in your own calendar) that any calendar app can subscribe to. Making a new link stops the old one.

Calendars Sophie can use

Connecting a Lovable app

  1. In Lovable, add a Supabase edge function (for example ansame-webhook) and store your signing secret as a secret called ANSAME_SECRET.
  2. In the function, read the raw body, check it with the code above, then save call and quote to your tables.
  3. Copy the function's public URL into ansame under Developers → Webhook URL and press Send test.
  4. To pull history, store your API key as ANSAME_KEY and call /api/v1/calls and /api/v1/quotes from a server function. Don't put the key in browser code.

Errors and limits

StatusMeaning
401Missing or wrong API key
403That item belongs to another business
404No such item or endpoint
429Too many bad keys from one address in 15 minutes

Errors come back as {"ok": false, "error": "…"}. Revoking a key in the dashboard stops it straight away.