Skip to main content

Call-events webhook

Wink shows a rep the customer they are talking to — the customer's order history, their SMS thread, live transcription — the moment the call connects. It learns that a call connected from your telephony platform, which posts a small JSON body to one of four URLs as each call event happens.

This document is the whole contract. It is vendor-neutral: any dialler that can make an HTTP POST can drive it.


1. Endpoints

Base URL: https://api-tjc.trywink.io

POST /call-events/call-started
POST /call-events/call-ended
POST /call-events/hold-started
POST /call-events/hold-ended

One URL per event, so a script that configures one HTTP action per event never has to build a body that names its own type. Content-Type is application/json.

The base URL identifies your brand. Post to the host you were given; a request to any other Wink host is rejected (see 404 below), and a key issued for one brand does not authenticate on another's host.


2. Authentication

Every request carries an API key in a header:

X-API-Key: wk_live_<40 hex characters>

Wink issues the key. It is shown exactly once at issue time and is stored only as a hash on our side, so we cannot recover it for you — if it is lost, ask for a new one and we revoke the old.

Handling guidance:

  • Store it in your platform's secret store or credential/secure-variable facility. Never in a script literal, a source repository, a ticket, or an email thread.
  • It is a bearer credential: anyone holding it can post events as your brand. Treat a leak as an incident and ask us to revoke — revocation takes effect immediately, and rejected calls are visible to you as 401.
  • Keys are scoped to one brand and to the call-events feature only. A key cannot read anything back out of Wink.
  • Send it over HTTPS only. Do not put it in a URL query string; it belongs in the header, where it stays out of proxy and access logs.
  • Ask for separate keys for separate senders (production dialler, test script) so one can be revoked without taking the other down.

3. Request bodies

POST /call-events/call-started

FieldRequiredNotes
call_idyesYour platform's id for the call, stable for its whole lifetime (e.g. the contact id). Every later event for the same call must repeat it.
agent_emailyesThe rep's single-sign-on email address, as Wink knows it. Case and surrounding whitespace do not matter.
phone_numberyesThe customer's number. E.164 preferred — see §5.
event_idnoYour unique id for this event. Supply it: it is what makes a retry safe (§6).
occurred_atnoISO-8601, ideally with a UTC offset (2026-08-14T09:15:22Z). Defaults to the time Wink received the request.
metadatanoAny JSON object — skill, campaign, queue, disposition, notes. Stored as sent.
{
"event_id": "1a2b3c-start",
"call_id": "4c9f0e21",
"agent_email": "rep@example.com",
"phone_number": "+447700900123",
"occurred_at": "2026-08-14T09:15:22Z",
"metadata": { "skill": "jewellery", "campaign": "spring" }
}

POST /call-events/call-ended

Required: call_id, agent_email. Optional: event_id, phone_number, occurred_at, metadata (a good place for disposition and wrap-up notes).

POST /call-events/hold-started and POST /call-events/hold-ended

Required: call_id, agent_email. Optional: event_id, occurred_at, metadata.

Rules that hold for every endpoint

  • phone_number is only required on call-started. The later events in a call are matched by call_id and inherit the number from the start event, so there is no need to repeat it (you may, and it is stored if you do).
  • occurred_at never causes a rejection. A missing, empty, or unparseable timestamp falls back to Wink's receive time rather than failing the request — a clock we cannot read is not worth a failed call event.
  • metadata may be omitted, or sent as null; both are treated as {}.
  • Extra fields you send that are not listed here are ignored, so you can add to your template without coordinating a release with us.
  • call_id and agent_email must be non-empty strings. That is the only validation strict enough to reject a request.

4. Responses

Every accepted event returns 200 with the same four fields:

{
"accepted": true,
"duplicate": false,
"agent_resolved": true,
"delivered_sockets": 1
}
FieldMeaning
acceptedAlways true on a 200. The event is durably recorded.
duplicatetrue when this event_id was already recorded — your retry arrived and was correctly ignored. No second event was raised. delivered_sockets is normally 0 on a duplicate; it is non-zero only in the rare case where the original attempt was recorded but never reached the rep, and your retry is what completed it.
agent_resolvedWhether agent_email matched an active Wink user for your brand.
delivered_socketsHow many of that rep's open Wink browser tabs received the event on the server that handled your request. Wink runs several servers, and a tab connected to a different one still receives the event without being counted here, so treat this as a lower bound — useful for "did anything happen", not as an exact tab count.

Two 200s worth understanding:

  • agent_resolved: false — the event was well-formed and is on file, but the email address does not match an active Wink user for your brand. It is deliberately not an error: rejecting it would make your platform retry something that will never succeed. Nothing was shown to any rep. This is the flag to alert on — in practice it means a mis-mapped address, a rep who has not yet completed a Wink login, or a leaver. Send us the email address and we will reconcile it.
  • delivered_sockets: 0 with agent_resolved: true — nothing was counted, most often because the rep has no Wink tab open right now. The call state is held for them either way, so if they open Wink mid-call they still see it. Because the count only covers the server that handled your request, a 0 can also mean the rep's tab is connected elsewhere and did receive the event. Not an error, and not worth retrying.

Error responses:

StatusMeaningWhat to do
401Missing, malformed, revoked, or wrong-brand API key.Do not retry. Check the key and the base URL; ask for a re-issue.
404The host you posted to is not one Wink serves for you.Do not retry. Fix the base URL.
422The body was not usable: it was not valid JSON, or call_id / agent_email / (on call-started) phone_number was missing or empty.Do not retry unchanged — it will fail identically. Fix the template.
5xxSomething failed on our side.Retry — see §6.

Two notes on 422:

  • The key is checked before the body, so a request with a bad key returns 401 and tells you nothing about the schema. The one exception is a body that is not syntactically valid JSON: that fails in the HTTP layer before anything else and returns 422 even when the key is also wrong. The response says only that the JSON could not be parsed and where — never which fields the endpoint expects.
  • When you get a 422 on a request you believe is correct, log the exact bytes you sent. Almost every case is a templating artefact: an unquoted substitution, an empty variable that rendered as "", or a trailing comma.

5. Phone numbers

Send E.164 — a leading +, country code, no spaces, hyphens, or brackets:

+447700900123 UK mobile
+442071838750 UK landline

If a number arrives in a national format (07700 900123, 020 7183 8750) Wink converts it using your brand's country, which for a UK brand is the United Kingdom. That conversion is a convenience, not a contract: a number we cannot place is stored as you sent it, and a rep may then see no customer match for a customer we actually know. Numbers that reach you already in E.164 should be passed through untouched — do not strip the +.

Withheld or unavailable caller ID: send the placeholder your platform provides rather than omitting phone_number, since call-started requires the field. The event will be recorded and the rep will simply see no customer match.


6. Retries and duplicates

Wink processes the event before responding, so a 200 means the event is recorded, not merely queued.

  • Retry on a timeout, a connection failure, or any 5xx. An exponential backoff — say 1s, 4s, 15s, then give up — is plenty; a call event that is minutes late has already missed the call it described.
  • Do not retry a 401, 404, or 422. Every one of them will fail the same way, and the retry only delays the alert to whoever can fix it.
  • Always send event_id, and reuse the same value on every retry of the same event. It is unique per brand, so a replayed event is recorded once and answered with duplicate: true. Retrying is therefore safe: a 200 you never saw because the connection dropped will not produce a second event for the rep.
  • If you cannot supply an event_id, use something you can regenerate deterministically — <call_id>-started, <call_id>-hold-1. Without one, Wink has no way to tell a retry from a genuinely new event, and a rep can see the same call announced twice.

Ordering is not required in the sense that nothing is rejected for arriving out of order: events are timestamped and applied as they arrive, and a hold-ended that overtakes its hold-started self-corrects on the next event. But an end or hold event for a call Wink never saw start is recorded without becoming live call state — there is no call on the rep's screen for it to change. Send call-started first for every call.


7. Getting started

  1. Get a test key — an owner mints one under Org Settings → API Keys (see API keys), or ask the Wink team during onboarding.
  2. Post a call-started for a real rep's email and a number you can dial, with that rep signed in to Wink, and confirm agent_resolved: true (and, with a single tab open, delivered_sockets: 1 — see the note above on why that count can read low).
  3. Post the matching call-ended and confirm the call clears from the rep's screen.
  4. Repeat the call-started with the same event_id and confirm you get duplicate: true — that is your retry path proven.
  5. Wire the four events into your dialler's call flow and ask for a production key.

If something looks wrong, send us the call_id, the event_id, and the timestamp of the request; every event is recorded against them and we can trace it end to end.