confirmation
Confirmation
One delivery or collection being checked with one customer. You create it; it closes itself when there’s an answer.
"id": "cf_7d2a9c41e0b3f658",
"status": "in_progress"
Send AnyoneIn the order. It emails, texts and calls the customer, understands whatever they reply, and sends your system a signed webhook with the answer.
curl -X POST \
https://api.anyonein.co.uk/v1/confirmations \
-H "Authorization: Bearer $ANYONEIN_KEY" \
-H "Content-Type: application/json" \
-d @priya-sofa.jsonHTTP/1.1 201 Created
{
"confirmation": {
"id": "cf_7d2a9c41e0b3f658",
"status": "scheduled",
"customer": { "first": "Priya" },
"order": { "date": "2026-10-13",
"window": { "from": "08:00", "to": "12:00" } },
"steps": [
{ "channel": "email", "state": "pending" },
{ "channel": "sms", "state": "pending" },
{ "channel": "call", "state": "pending" }
]
}
}
Sunday 11 October · 12:41
I’m at work but my partner Jo will sign
{
"type": "confirmation.updated",
"data": {
"id": "cf_7d2a9c41e0b3f658",
"status": "confirmed",
"outcome": {
"intent": "alternate_signer",
"summary": "Confirmed; partner Jo will sign.",
"signerName": "Jo",
"channel": "sms"
}
}
}
Send the customer, the items, the date and window, and the slots you could offer instead. AnyoneIn plans the conversation and works through it on its own.
curl -X POST https://api.anyonein.co.uk/v1/confirmations \
-H "Authorization: Bearer $ANYONEIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalId": "HF-100482",
"brand": {
"name": "Harlow & Finch",
"colors": { "primary": "#1F3A5F" },
"supportEmail": "help@harlowandfinch.co.uk",
"tone": "warm, clear and brief"
},
"customer": {
"name": "Priya Shah",
"phone": "07700 900123",
"email": "priya@example.com"
},
"order": {
"ref": "HF-100482",
"type": "delivery",
"date": "2026-10-13",
"window": { "from": "08:00", "to": "12:00" },
"items": [{ "name": "Marlow 3-seat sofa", "qty": 1, "sku": "MAR-3S-OAT" }],
"address": { "line1": "14 Albert Road", "city": "Bristol", "postcode": "BS2 0XA" },
"rescheduleOptions": [
{ "date": "2026-10-15", "window": { "from": "08:00", "to": "12:00" } }
]
},
"cadence": "standard",
"webhookUrl": "https://example.com/webhooks/anyonein"
}'// Node 18+ (global fetch)
const res = await fetch('https://api.anyonein.co.uk/v1/confirmations', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.ANYONEIN_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
externalId: 'HF-100482',
brand: {
name: 'Harlow & Finch',
colors: { primary: '#1F3A5F' },
supportEmail: 'help@harlowandfinch.co.uk',
tone: 'warm, clear and brief',
},
customer: { name: 'Priya Shah', phone: '07700 900123', email: 'priya@example.com' },
order: {
ref: 'HF-100482',
type: 'delivery',
date: '2026-10-13',
window: { from: '08:00', to: '12:00' },
items: [{ name: 'Marlow 3-seat sofa', qty: 1, sku: 'MAR-3S-OAT' }],
address: { line1: '14 Albert Road', city: 'Bristol', postcode: 'BS2 0XA' },
rescheduleOptions: [{ date: '2026-10-15', window: { from: '08:00', to: '12:00' } }],
},
cadence: 'standard',
webhookUrl: 'https://example.com/webhooks/anyonein',
}),
});
const body = await res.json();
if (!res.ok) throw new Error(body.errors?.join(' ') ?? body.error);
console.log(body.confirmation.id, body.confirmation.status); // cf_7d2a9c41e0b3f658 scheduledimport os
import requests
res = requests.post(
"https://api.anyonein.co.uk/v1/confirmations",
headers={"Authorization": f"Bearer {os.environ['ANYONEIN_KEY']}"},
json={
"externalId": "HF-100482",
"brand": {
"name": "Harlow & Finch",
"colors": {"primary": "#1F3A5F"},
"supportEmail": "help@harlowandfinch.co.uk",
"tone": "warm, clear and brief",
},
"customer": {"name": "Priya Shah", "phone": "07700 900123", "email": "priya@example.com"},
"order": {
"ref": "HF-100482",
"type": "delivery",
"date": "2026-10-13",
"window": {"from": "08:00", "to": "12:00"},
"items": [{"name": "Marlow 3-seat sofa", "qty": 1, "sku": "MAR-3S-OAT"}],
"address": {"line1": "14 Albert Road", "city": "Bristol", "postcode": "BS2 0XA"},
"rescheduleOptions": [{"date": "2026-10-15", "window": {"from": "08:00", "to": "12:00"}}],
},
"cadence": "standard",
"webhookUrl": "https://example.com/webhooks/anyonein",
},
timeout=15,
)
body = res.json()
if not res.ok:
raise RuntimeError(body.get("errors") or body["error"])
print(body["confirmation"]["id"], body["confirmation"]["status"])<?php
$payload = [
'externalId' => 'HF-100482',
'brand' => [
'name' => 'Harlow & Finch',
'colors' => ['primary' => '#1F3A5F'],
'supportEmail' => 'help@harlowandfinch.co.uk',
'tone' => 'warm, clear and brief',
],
'customer' => ['name' => 'Priya Shah', 'phone' => '07700 900123', 'email' => 'priya@example.com'],
'order' => [
'ref' => 'HF-100482',
'type' => 'delivery',
'date' => '2026-10-13',
'window' => ['from' => '08:00', 'to' => '12:00'],
'items' => [['name' => 'Marlow 3-seat sofa', 'qty' => 1, 'sku' => 'MAR-3S-OAT']],
'address' => ['line1' => '14 Albert Road', 'city' => 'Bristol', 'postcode' => 'BS2 0XA'],
'rescheduleOptions' => [['date' => '2026-10-15', 'window' => ['from' => '08:00', 'to' => '12:00']]],
],
'cadence' => 'standard',
'webhookUrl' => 'https://example.com/webhooks/anyonein',
];
$ch = curl_init('https://api.anyonein.co.uk/v1/confirmations');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('ANYONEIN_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 201) {
throw new RuntimeException(implode(' ', $body['errors'] ?? [$body['error']]));
}
echo $body['confirmation']['id'];Three one-tap buttons: I’ll be in, someone else will be in, I need a different day.
If the email went unanswered. Plain-English replies are understood.
A natural voice that says it’s automated, and rebooks on the spot.
confirmation.updated, signed, with the status and a one-line summary.
Most teams have their first confirmation running in the sandbox in about 30 minutes, most of it reading.
We send it with your webhook signing secret. Keep both on your server. In the sandbox nothing reaches a real phone or inbox.
Ask for a sandbox keyexport ANYONEIN_KEY="your-sandbox-key"
curl https://api.anyonein.co.uk/v1/confirmations?limit=1 \
-H "Authorization: Bearer $ANYONEIN_KEY"
# {"confirmations":[]}const headers = { Authorization: `Bearer ${process.env.ANYONEIN_KEY}` };
const res = await fetch('https://api.anyonein.co.uk/v1/confirmations?limit=1', { headers });
console.log(res.status); // 200 (401 means the key is wrong)headers = {"Authorization": f"Bearer {os.environ['ANYONEIN_KEY']}"}
res = requests.get("https://api.anyonein.co.uk/v1/confirmations?limit=1", headers=headers)
print(res.status_code) # 200 (401 means the key is wrong)<?php
$ch = curl_init('https://api.anyonein.co.uk/v1/confirmations?limit=1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('ANYONEIN_KEY')],
]);
curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE); // 200 (401 means the key is wrong)Customer, date, window, at least one item. Use "cadence": "demo" while testing and the steps run about 90 seconds apart.
curl -X POST https://api.anyonein.co.uk/v1/confirmations \
-H "Authorization: Bearer $ANYONEIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer": { "name": "Priya Shah", "phone": "07700 900123", "email": "priya@example.com" },
"order": {
"date": "2026-10-13",
"window": { "from": "08:00", "to": "12:00" },
"items": [{ "name": "Marlow 3-seat sofa" }]
},
"cadence": "demo",
"webhookUrl": "https://example.com/webhooks/anyonein"
}'const res = await fetch('https://api.anyonein.co.uk/v1/confirmations', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.ANYONEIN_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
customer: { name: 'Priya Shah', phone: '07700 900123', email: 'priya@example.com' },
order: { date: '2026-10-13', window: { from: '08:00', to: '12:00' }, items: [{ name: 'Marlow 3-seat sofa' }] },
cadence: 'demo',
webhookUrl: 'https://example.com/webhooks/anyonein',
}),
});res = requests.post(
"https://api.anyonein.co.uk/v1/confirmations",
headers={"Authorization": f"Bearer {os.environ['ANYONEIN_KEY']}"},
json={
"customer": {"name": "Priya Shah", "phone": "07700 900123", "email": "priya@example.com"},
"order": {"date": "2026-10-13", "window": {"from": "08:00", "to": "12:00"}, "items": [{"name": "Marlow 3-seat sofa"}]},
"cadence": "demo",
"webhookUrl": "https://example.com/webhooks/anyonein",
},
)<?php
$payload = [
'customer' => ['name' => 'Priya Shah', 'phone' => '07700 900123', 'email' => 'priya@example.com'],
'order' => ['date' => '2026-10-13', 'window' => ['from' => '08:00', 'to' => '12:00'], 'items' => [['name' => 'Marlow 3-seat sofa']]],
'cadence' => 'demo',
'webhookUrl' => 'https://example.com/webhooks/anyonein',
];Reply as the customer in the sandbox console. When there’s an answer, a signed confirmation.updated event lands on your endpoint.
POST /webhooks/anyonein HTTP/1.1
Content-Type: application/json
X-AnyoneIn-Signature: sha256=9f2c41e8d0b7a6f3c5e1d2b4a7968f0e3c1b5a2d4e6f8091a3b5c7d9e1f20468
{"type":"confirmation.updated","data":{"id":"cf_7d2a9c41e0b3f658","status":"confirmed",…}}if (event.type === 'confirmation.updated') {
const c = event.data;
if (c.status === 'confirmed') markReadyForDispatch(c.externalId, c.outcome.signerName);
if (c.status === 'rescheduled') moveBooking(c.externalId, c.order.date, c.order.window);
if (c.status === 'action_needed' || c.status === 'unreachable') flagForTeam(c.externalId, c.outcome.summary);
}A confirmation carries the order, the plan, every message in and out, and the answer. Four ideas cover the whole API.
confirmation
One delivery or collection being checked with one customer. You create it; it closes itself when there’s an answer.
"id": "cf_7d2a9c41e0b3f658",
"status": "in_progress"
steps[]
The plan: an email, then a text, then a call, each with a dueAt and a state. A reply pauses them.
{ "channel": "sms",
"state": "delivered" }
events[]
The timeline: messages out, replies in, each line of the call, and what the agent understood from them.
{ "kind": "in", "channel": "sms",
"text": "my partner Jo will sign" }
outcome
The answer, as an intent and a one-line summary for ops, plus the signer’s name or the new slot.
"intent": "alternate_signer",
"signerName": "Jo"
Two open states, five ways to close.
Someone will be in, maybe a named signer. Dispatch.
They picked one of your other slots. Move the booking.
A different day, cancel, wrong person, opt-out or wants a person.
Everyone tried, nobody answered. Call before dispatch.
You stopped it through the API.
standard for customers, demo for testing.
standard
Texts and calls 9am–8pm only. One retry for an unanswered call. Unreachable 6 hours after the last step.
demo
About 90 seconds apart, so you can watch the whole flow in a few minutes. Not for real customers.
Every webhook carries an HMAC-SHA256 of its exact body, keyed with your signing secret. Check it before you act on anything.
X-AnyoneIn-Signature: sha256=HMAC_SHA256(secret, rawBody)
timingSafeEqual, compare_digest, hash_equals.data.id and updatedAt; the newest wins.Roadmap Automatic retries, a signed timestamp for replay protection, and secret rotation.
import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';
const app = express();
// Verify against the raw bytes, before any JSON parsing.
app.post('/webhooks/anyonein', express.raw({ type: 'application/json' }), (req, res) => {
const expected = 'sha256=' + createHmac('sha256', process.env.ANYONEIN_WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
const given = req.get('X-AnyoneIn-Signature') ?? '';
const ok = given.length === expected.length &&
timingSafeEqual(Buffer.from(given), Buffer.from(expected));
if (!ok) return res.status(401).end();
const { type, data } = JSON.parse(req.body);
if (type === 'confirmation.updated') {
// data is the full confirmation: data.status, data.outcome, data.externalId ...
}
res.sendStatus(204);
});import hashlib
import hmac
import json
import os
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["ANYONEIN_WEBHOOK_SECRET"].encode()
@app.post("/webhooks/anyonein")
def anyonein_webhook():
raw = request.get_data() # the exact bytes we signed
expected = "sha256=" + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
given = request.headers.get("X-AnyoneIn-Signature", "")
if not hmac.compare_digest(given, expected):
abort(401)
event = json.loads(raw)
if event["type"] == "confirmation.updated":
confirmation = event["data"] # status, outcome, externalId ...
return "", 204<?php
$raw = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, getenv('ANYONEIN_WEBHOOK_SECRET'));
$given = $_SERVER['HTTP_X_ANYONEIN_SIGNATURE'] ?? '';
if (!hash_equals($expected, $given)) {
http_response_code(401);
exit;
}
$event = json_decode($raw, true);
if ($event['type'] === 'confirmation.updated') {
$confirmation = $event['data']; // status, outcome, externalId ...
}
http_response_code(204);In the sandbox, email, text and calls run as a simulator. Nothing reaches a real phone. You reply as the customer from the sandbox console, and the same outcomes and webhooks fire as in production. Try a reply:
Harlow & Finch: Hi Priya, your Marlow 3-seat sofa is due Tue 13 Oct, 8am–12pm. It needs a signature. Will you be in? Reply YES, or tell us what suits you. api.anyonein.co.uk/c/7d2a…
// Pick a reply on the left.
A simulation of the sandbox on this page, with canned answers. The real agent reads any reply in plain English.
Take a 60-day pilot with a hold-out group, and see the difference in your own numbers.