Lynkist Developers

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-Version response header on every Public API response, errors included.
  • Added — Deprecation, Sunset and Link headers 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"}}. Send limit (1–100, default 20) and cursor (the next_cursor of the previous page). Lists are newest first. This replaces page / limit with total_items on 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 code ERROR, NOT_FOUND or CONFLICT.
  • 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_REQUIRED is always 403 (the plan does not include the feature).
  • Changed — error.details of a VALIDATION_ERROR is a list of {"field", "code", "message"}.
  • Security — a validation error no longer returns the values that were sent.
  • Changed — a missing scope answers INSUFFICIENT_SCOPE with required_scopes and granted_scopes (were required_permissions and current_permissions).
  • Changed — a failure on our side or at WhatsApp answers 5xx (WHATSAPP_UNAVAILABLE 503, UPSTREAM_ERROR 502, SEND_OUTCOME_UNKNOWN 504), never 4xx. Retry 5xx and 429 with exponential backoff and obey Retry-After.
  • Added — send errors carry retryable (true or false).
  • Fixed — meta.request_id of an error is the request's id, the same value as the X-Request-Id response header.

Rate limits

  • Changed — a request that we answer with 429 or 5xx does not count against the daily quota.
  • Added — a 429 carries window (minute or day) and retry_after in the error, and the Retry-After header.

Resources

  • Changed — DELETE of a contact, a media file or a webhook endpoint answers {"id": "...", "deleted": true}.
  • Fixed — contact first_name and last_name accept at most 50 characters, company and job_title at 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 form whsec_.... The X-Lynkist-* headers and the sha256=<hex> signature are removed.
  • Added — Lynkist-Version, Lynkist-Event, Lynkist-Delivery-Id and Lynkist-Webhook-Id headers 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_version in the event body is the exact version ("1.0"), not a date.
  • Changed — the data of every event has an object: the resource, in the shape the Public API returns it.
  • Changed — message.received is 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_received carries the Flow, the contact and the answers as flat name: value pairs. Before, it had only an id.
  • Added — contact.created is also sent for a contact that Lynkist creates itself (a first WhatsApp message, a send to a new number, a website visitor). It carries source.
  • Changed — contact.*, followup.*, contact.note_added and contact.communication_added carry 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 Gone is disabled at once, with no retry.

Messages

  • Changed (2026-10-16, before versioning) — POST /messages/template: language no longer defaults to en. 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/template validates to: an E.164 number with its country code. An invalid number answers 400 VALIDATION_ERROR at once. The response to is 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 as POST /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 return 400 (VALIDATION_ERROR), not 422. 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}, and GET /campaigns/{id}/audience/preview returned 401/500 due to internal wiring bugs; all now function. Error messages no longer leak stringified internal dicts. Duplicate contact phone/email now returns 409 (was 500). The contact.deleted webhook now fires reliably.
  • Changed (docs) — Base URL is https://lynkist.io/api/v1/public (the api.lynkist.io host was never live). Removed references to a "sandbox" environment — there is none; live/test keys hit the same API. Documented the campaign variable_mapping shape 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 five X-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/meta envelope).

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 FastAPI sub-app containing only public_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 to get_rate_limits_for_tenant is 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.

Changelog | Lynkist Developers