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
- Log in to the business's dashboard and open Developers.
- Paste your endpoint URL (it must start with
https://) and save. ansame creates a signing secret for you. - Press Send test. You'll see the delivery, the status code your server gave and its reply in the log.
- 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.
| Header | What it holds |
|---|---|
X-Ansame-Event | call.completed, or test for the Send test button |
X-Ansame-Delivery | A unique id for this delivery. Retries keep the same id, so you can ignore repeats. |
X-Ansame-Timestamp | Unix time in seconds when this try was sent |
X-Ansame-Signature | sha256= 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_…"
| Endpoint | Returns |
|---|---|
GET /business | The business the key belongs to |
GET /calls | Calls and messages, newest first. Options: limit (1–200, default 50), since (an ISO date) |
GET /quotes | Quotes, newest first. Options: limit, since, status (draft, sent, won, lost) |
GET /quotes/{id} | One quote with all its lines |
Quote object
| Field | Meaning |
|---|---|
ref | Human reference, e.g. Q-2026-0001 |
status | draft, sent, won or lost |
customer | name, phone, email, address, postcode |
job, notes | What the caller wants, and anything to check |
lines[] | description, qty, unit, unit_price, total, source (why it was added) |
subtotal, vat_rate, vat, total | In pounds. vat_rate is 0.2 for VAT-registered businesses, otherwise 0 |
wording | The "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
| Event | When |
|---|---|
call.completed | As before, now with a bookings array: every booking made on that call |
booking.created | A booking was confirmed (by phone or in the dashboard) |
booking.requested | Calendly couldn't take it automatically; link is the Calendly page for that time, to send the caller |
booking.cancelled | The 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
- ansame calendar: free times are the business's hours, minus breaks, days off and existing bookings (with each appointment's gap after it).
- Calendly: the business pastes a personal access token (stored encrypted, never shown again). Sophie offers times that are free in Calendly and inside the business's hours, and books with Calendly's scheduling API. That needs a paid Calendly plan; on other plans the booking is saved as a request and the business gets the Calendly link for that time.
- Google Calendar and Outlook / Microsoft 365: built, switched off until ansame has its own Google and Microsoft apps.
Connecting a Lovable app
- In Lovable, add a Supabase edge function (for example
ansame-webhook) and store your signing secret as a secret calledANSAME_SECRET. - In the function, read the raw body, check it with the code above, then save
callandquoteto your tables. - Copy the function's public URL into ansame under Developers → Webhook URL and press Send test.
- To pull history, store your API key as
ANSAME_KEYand call/api/v1/callsand/api/v1/quotesfrom a server function. Don't put the key in browser code.
Errors and limits
| Status | Meaning |
|---|---|
401 | Missing or wrong API key |
403 | That item belongs to another business |
404 | No such item or endpoint |
429 | Too 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.