Changelog
API surface changes — new endpoints, new fields, deprecations, breaking changes.
This page lists every change to the Public API and to webhooks, by version. The API and the
webhooks have one version, MAJOR.MINOR: a minor version is backward compatible, and a change
that can break a client is a new major version. See Versioning.
Each entry is one of:
- Added — a new endpoint, field, scope, event or header
- Changed — behaviour that is different from before
- Deprecated — still works, but will be removed in a later major version
- Removed — gone (only in a major version)
- Fixed — a bug fix that changes what you can observe
- Security — hardening that integrators should know about
1.0
The first versioned release. 1.0 is the contract from this release on. The
changes below are relative to the API before versioning; several are breaking,
which is why they are made now, before the contract is frozen.
Versioning
- Added —
Lynkist-Versionresponse header on every Public API response, errors included. - Added —
Deprecation,SunsetandLinkheaders on a deprecated major version (RFC 9745, RFC 8594).
Pagination
- Changed — every list endpoint uses cursor pagination and answers
{"data": [...], "pagination": {"next_cursor", "has_more", "limit"}}. Sendlimit(1–100, default 20) andcursor(thenext_cursorof the previous page). Lists are newest first. This replacespage/limitwithtotal_itemson Contacts, Contact search, Templates and Webhook deliveries. - Changed — Media, Campaigns and Webhook endpoints lists are now paginated (they returned every row).
Errors
- Changed — every error has a stable code from a published list
(the error code list). Each code has one HTTP status. For example
INVALID_API_KEY,INSUFFICIENT_SCOPE,CONTACT_NOT_FOUND,CONTACT_ALREADY_EXISTS,TEMPLATE_NOT_FOUND,INSUFFICIENT_WALLET_BALANCE,RECIPIENT_UNDELIVERABLE,RATE_LIMIT_EXCEEDED. Before, most errors had the codeERROR,NOT_FOUNDorCONFLICT. - Changed — an error of the Campaigns, Templates and Media endpoints has a
code of the same list (
CAMPAIGN_NOT_FOUND,INVALID_CAMPAIGN_STATE,TEMPLATE_LOCKED,TEMPLATE_EDIT_QUOTA_EXCEEDED, ...). No endpoint returns an internal code or a provider's name. An error with no specific code has the code of its status (INVALID_REQUEST,NOT_FOUND,CONFLICT, ...). - Changed — no active WhatsApp account answers
404 WHATSAPP_NOT_CONNECTED. - Changed — a plan limit answers
429 PLAN_LIMIT_REACHED.UPGRADE_REQUIREDis always403(the plan does not include the feature). - Changed —
error.detailsof aVALIDATION_ERRORis a list of{"field", "code", "message"}. - Security — a validation error no longer returns the values that were sent.
- Changed — a missing scope answers
INSUFFICIENT_SCOPEwithrequired_scopesandgranted_scopes(wererequired_permissionsandcurrent_permissions). - Changed — a failure on our side or at WhatsApp answers
5xx(WHATSAPP_UNAVAILABLE503,UPSTREAM_ERROR502,SEND_OUTCOME_UNKNOWN504), never4xx. Retry5xxand429with exponential backoff and obeyRetry-After. - Added — send errors carry
retryable(true or false). - Fixed —
meta.request_idof an error is the request's id, the same value as theX-Request-Idresponse header.
Rate limits
- Changed — a request that we answer with
429or5xxdoes not count against the daily quota. - Added — a
429carrieswindow(minuteorday) andretry_afterin the error, and theRetry-Afterheader.
Resources
- Changed —
DELETEof a contact, a media file or a webhook endpoint answers{"id": "...", "deleted": true}. - Fixed — contact
first_nameandlast_nameaccept at most 50 characters,companyandjob_titleat most 100. Longer values answered a server error.
Webhooks
Webhook 1.0 is a new contract for every event. Each event has a published
schema in the event catalog.
- Changed — deliveries follow the Standard Webhooks specification
(standardwebhooks.com). Headers:
webhook-id,webhook-timestamp,webhook-signature(v1,<base64 HMAC-SHA256>of"{id}.{timestamp}.{body}"). Signing secrets have the formwhsec_.... TheX-Lynkist-*headers and thesha256=<hex>signature are removed. - Added —
Lynkist-Version,Lynkist-Event,Lynkist-Delivery-IdandLynkist-Webhook-Idheaders on every delivery. - Added — each endpoint has an
api_version. Send it when you create or update an endpoint; the default is the latest major. An endpoint keeps its major until you change it. - Changed —
api_versionin the event body is the exact version ("1.0"), not a date. - Changed — the
dataof every event has anobject: the resource, in the shape the Public API returns it. - Changed —
message.receivedis one event for each message, with the contact, the message type and the text. Before, WhatsApp sent one event for a batch with only a count. - Changed —
flow.submission_receivedcarries the Flow, the contact and the answers as flatname: valuepairs. Before, it had only an id. - Added —
contact.createdis also sent for a contact that Lynkist creates itself (a first WhatsApp message, a send to a new number, a website visitor). It carriessource. - Changed —
contact.*,followup.*,contact.note_addedandcontact.communication_addedcarry documented fields only. - Added — rotating a secret keeps the old secret valid for 24 hours; deliveries in that time carry both signatures.
- Changed — an endpoint that answers
410 Goneis disabled at once, with no retry.
Messages
- Changed (2026-10-16, before versioning) —
POST /messages/template:languageno longer defaults toen. Leave it out when the template has one approved language; send it when the template has more than one. - Changed (2026-10-16, before versioning) —
POST /messages/templatevalidatesto: an E.164 number with its country code. An invalid number answers400 VALIDATION_ERRORat once. The responsetois always E.164.
Before versioning
Changes made before version 1.0, by date.
2026-10-24
Added — WhatsApp message.sent, message.delivered, message.read and message.failed
webhook events carry sent_by: human, ai_agent, chatbot, api, campaign,
integration or system (null for messages sent before this). See
Webhooks.
Changed — a template sent with POST /messages/template no longer moves its chat to the top
of the dashboard inbox; a chat with only API messages is listed under Automated until the
customer replies. The API response is unchanged. See
Messages.
2026-10-16
Added — PUT /templates/{id} edits a template's category or components (scope
templates:write). Meta's limits apply: an approved or paused template can be edited 1 time in
24 hours and 10 times in 30 days; over the limit the call returns 429
TEMPLATE_EDIT_QUOTA_EXCEEDED with error.edit_quota.next_edit_at. A pending template cannot be
edited. See Templates.
Fixed — POST /messages/template returned message_id: null on every successful send. It
now returns the WhatsApp wamid, so you can match the send to its message.* webhook events.
Changed — POST /messages/template checks to. A number must be E.164 with its country
code; the + is optional, and spaces and hyphens are removed. Letters, a number without a
country code, or a number that is not valid for any country return 400 VALIDATION_ERROR
naming to. Before, any string was accepted and the send failed later at WhatsApp. The
response to is now always E.164.
Changed — POST /messages/template finds the template on the WABA that sends it, and
matches language exactly. language no longer defaults to en: leave it out when the
template has one approved language, and send it when the template has more than one (else
400). The endpoint also documents 402 (not enough wallet credit) and 409.
Fixed (docs) — the Templates examples now carry the example
values that every variable needs. The old examples used {{1}} without them, and the API
refused them with 400.
2026-08-16
Added — GET /{tenant}/api/v1/settings/api-keys/scopes returns the scope vocabulary and
the named presets the backend honours (scopes, wildcard, presets). The dashboard's
key-creation form now renders from it instead of keeping its own copy.
Fixed — POST /contacts/search now applies the tag filter. The field has always been
part of the request schema, but the handler was not forwarding it, so a tagged search returned
an unfiltered result set with no error. If you were compensating by filtering client-side, you
can stop.
Fixed (dashboard) — the API-key Read Only preset now includes campaigns:read, and the
Campaigns preset is selectable. Keys previously minted from the dashboard as "read only"
lacked campaigns:read and returned 403 on GET /campaigns, disagreeing with the preset
table in Authentication. Rotate any affected key to pick up the
corrected scopes.
Fixed (docs) — a content audit against the deployed code corrected:
- The webhook event catalogue is 24 events, not 22 as the developer-hub landing card said. The Webhooks page itself was already correct.
- The endpoint-reachability probe is
POST /api/v1/integrations/webhooks/endpoints/verify(dashboard-only, JWT). It was previously documented asPOST /webhooks/verify, which does not exist on the Public API. - Custom daily quotas are offered on the Business plan; the rate-limit page referred to an "enterprise" plan that has never existed.
- The hub landing no longer claims every dashboard action is exposed over REST. Six resources are; the shared inbox is receive-only, via webhooks.
2026-07-27
Fixed — POST /webhooks/{id}/test now sends the test event synchronously and returns
what your endpoint actually answered.
Previously it queued the event and returned {"message": "Test webhook queued", "delivery_id": …} — a 200 that told you nothing about the outcome. Worse, if the endpoint had been
auto-disabled after 20 consecutive failures, the queued delivery was dropped immediately with
"Endpoint not found or disabled" while the API still reported success.
Changed (response shape) — the endpoint now returns delivered, status_code,
response_time_ms, response_body, error and endpoint_disabled alongside delivery_id.
The old message field is still present, now describing the outcome. Branch on delivered,
not on the HTTP status of the call itself. Test sends also run against disabled endpoints (so a
fix can be confirmed before re-enabling) and never count toward auto-disable. See
Webhooks API.
2026-06-28
Fixed — A parity audit against the deployed API corrected several docs and fixed bugs:
- Docs — Errors now documents the real envelope
(
{ "success": false, "error": { "code", "message" }, "meta": … }), not{"detail": …}; validation failures return400(VALIDATION_ERROR), not422. Rate Limits corrected to the real model — a flat 120 req/min burst plus a plan-based daily quota (the per-plan minute tiers and the "default 60/1000" note were never accurate). Authentication: IP allowlist & expiry are enforced but not yet settable via the create API. Pagination only applies to Contacts & Templates lists. - Fixed (API) —
POST /messages/template,POST /templates,POST /templates/{id}/submit,POST /contacts,POST /webhooks,POST /campaigns/{id}/{send,schedule,resume}, andGET /campaigns/{id}/audience/previewreturned401/500due to internal wiring bugs; all now function. Error messages no longer leak stringified internal dicts. Duplicate contact phone/email now returns409(was500). Thecontact.deletedwebhook now fires reliably. - Changed (docs) — Base URL is
https://lynkist.io/api/v1/public(theapi.lynkist.iohost was never live). Removed references to a "sandbox" environment — there is none;live/testkeys hit the same API. Documented the campaignvariable_mappingshape in full. Added an importable Postman collection.
2026-06-01
Security — openapi.json, /docs (Swagger UI), and /redoc are now disabled in
production. The Lynkist backend hosts internal, dashboard and Public API routers in a single
FastAPI process, so a public OpenAPI document would enumerate every internal route, request
schema, and field name. A filtered, public-API-only OpenAPI explorer is on the roadmap. In
dev/staging the explorers continue to work.
Added — First comprehensive Developer Hub release, covering the existing API surface:
- Per-resource API reference pages: Contacts, Templates, Messages, Media, Campaigns, Webhook management.
- Rate Limits page documenting per-plan quotas and the five
X-RateLimit-*headers emitted on every response. - Authentication rewritten with the real key format
(
lk_sk_{env}_{40-hex}), the 12-scope catalogue, the 4 preset bundles, and IP-allowlist behaviour. - Webhooks rewritten with the real 22-event catalog (contacts,
follow-ups, messages, campaigns, lifecycle), the actual signature recipe
(
sha256(secret, "{ts}.{body}") → sha256=<hex>), the fiveX-Lynkist-*headers, and the real retry curve (1m / 5m / 30m / 2h / 6h, then abandon). - Errors page added (later corrected on 2026-06-28 to the real
success/error/metaenvelope).
Earlier
Prior to this release, the Public API surface evolved in lockstep with the dashboard and was documented only in code. The endpoint catalogue documented today reflects the deployed surface as of 2026-06-01.
Anything older than the dates above is not separately enumerated — open a GitHub issue if you need a specific historical confirmation.
Roadmap (not yet shipped)
These are tracked elsewhere; this list is included to set expectations and reduce duplicate support questions. Dates are not committed — they shift.
- Template approval webhooks (
template.approved,template.rejected,template.disabled). The events exist in the catalogue plan but the domain code does not emit them yet. - Per-resource OpenAPI explorer. A
FastAPIsub-app containing onlypublic_api_router, mounted at a public URL with Swagger UI. - Live plan-tier resolution. Today every tenant gets the default
(60/min, 1000/day)budget; the wiring from billing toget_rate_limits_for_tenantis queued. - OAuth-style delegated access for third-party integrations operating across multiple tenants.
- Conversations API — read inbound messages and reply within the 24-hour session window from the API.
If something here is blocking your integration, mention it in a support ticket — concrete demand moves dates.