These docs are for v4.0. Click to read the latest docs for v2026-03-15.

Migration guide: v4 to latest stable

The v4 (Contact Tracking) API is deprecated and will be sunset on June 15, 2027 — after this date, v4 endpoints may stop working. This guide describes how to migrate every v4 endpoint to the latest stable Omnisend API version.

Overview

The v4 API consists of three endpoints. All of their functionality is available in the latest stable version (2026-03-15):

v4 endpointLatest stable replacement
POST /v4/identifyPOST /api/contacts (Contacts API)
GET /v4/contacts/id:{id}GET /api/contacts/{id}
GET /v4/contacts/email:{email}GET /api/contacts?email={email}
GET /v4/contacts/phoneNumber:{phone}GET /api/contacts?phone={phone}
POST /v4/trackPOST /api/events (Events API)

Key differences that apply to all requests:

v4Latest stable
Base URLhttps://api.omnisend.com/v4/...https://api.omnisend.com/api/...
VersioningIn the URL path (/v4/)Omnisend-Version: 2026-03-15 header (required)
AuthenticationOAuth 2.0 Bearer token onlyOAuth 2.0 Bearer token

Authentication

Your existing OAuth 2.0 integration keeps working — the latest stable version supports the same Authorization Code Grant flow with the same scopes (contacts.read, contacts.write, events.write). Keep sending Authorization: Bearer {access-token} and add the Omnisend-Version header:

GET https://api.omnisend.com/api/contacts/60fe7581b0aa9fa6e928f794
Authorization: Bearer YOUR_ACCESS_TOKEN
Omnisend-Version: 2026-03-15

See the OAuth page of the latest stable version for the full flow.

The latest stable version also supports API key authentication — an option for single-store integrations, but not a replacement for OAuth in apps that serve multiple merchants.

Migrating POST /v4/identify

Use POST /api/contacts, which also creates a contact or updates an existing one.

The main structural change: email and phoneNumber are no longer top-level contact properties. Contacts are now identified by an identifiers array, where each identifier has a type (email or phone), the identifier value in id, and optional per-channel subscription statuses and consent data.

v4 request:

POST /v4/identify
{
  "email": "[email protected]",
  "phoneNumber": "+443031237300",
  "firstName": "Vanessa",
  "lastName": "Kensington",
  "tags": ["spy", "gadgets"],
  "optIns": [
    { "channel": "email", "createdAt": "2021-06-20T01:00:00Z" }
  ],
  "consents": [
    {
      "channel": "email",
      "source": "form:wheelOfFortune",
      "createdAt": "2021-06-20T01:00:00Z",
      "ip": "73.240.147.113",
      "userAgent": "Mozilla/5.0 ..."
    }
  ],
  "customProperties": { "age": 33, "married": true }
}

Latest stable request:

POST /api/contacts
{
  "identifiers": [
    {
      "type": "email",
      "id": "[email protected]",
      "channels": {
        "email": { "status": "subscribed", "statusChangedAt": "2021-06-20T01:00:00Z" }
      },
      "consent": {
        "source": "form:wheelOfFortune",
        "createdAt": "2021-06-20T01:00:00Z",
        "ip": "73.240.147.113",
        "userAgent": "Mozilla/5.0 ..."
      }
    },
    {
      "type": "phone",
      "id": "+443031237300"
    }
  ],
  "firstName": "Vanessa",
  "lastName": "Kensington",
  "tags": ["spy", "gadgets"],
  "customProperties": { "age": 33, "married": true }
}

Property mapping:

v4 propertyLatest stable
emailidentifiers[] entry with "type": "email", value in id
phoneNumberidentifiers[] entry with "type": "phone", value in id
optIns / optOutsidentifiers[].channels.{channel}.status: subscribed, unsubscribed or nonSubscribed
consentsidentifiers[].consent object (no channel field — consent belongs to the identifier)
firstName, lastName, gender, birthdate, address, city, state, country, countryCode, postalCode, tags, customPropertiesUnchanged

Behavioral differences:

  • v4 responded with 202 Accepted and processed the contact asynchronously. The latest stable endpoint is synchronous — it returns 200/201 with the stored contact (including its id).
  • The new endpoint supports sendWelcomeMessage per identifier (defaults to true).
  • For partial updates of an existing contact, you can also use PATCH /api/contacts/{id} or PATCH /api/contacts?email={email}.

Migrating GET /v4/contacts/{property}:{identifier}

The lookup-by-property URL format is replaced by two endpoints:

  • By ID: GET /v4/contacts/id:{id}GET /api/contacts/{id}
  • By email or phone: use the list endpoint with a filter:
GET https://api.omnisend.com/api/[email protected]
GET https://api.omnisend.com/api/contacts?phone=%2B443031237300
Authorization: Bearer YOUR_ACCESS_TOKEN
Omnisend-Version: 2026-03-15

Note that the filtered list returns an array of contacts (with cursor-based pagination via after/before), not a single object — take the first element. Remember to URL-encode phone numbers (+%2B).

The contact response format also follows the new identifiers structure described above.

Migrating POST /v4/track

Use POST /api/events.

🚧

Important: keep your automations and segments working

Omnisend identifies each event internally by its name, origin and version. Events sent via /v4/track carried no origin and no version, so any automations and segments configured for them are bound to that exact identity. If you send the same event through /api/events with an origin or eventVersion included in its identity, it becomes a different event — existing automations and segments will silently stop reacting to it.

For every event you already tracked via /v4/track, follow the rules below.

For previously tracked events:

  1. Pass the compatibility: no-origin header with every /api/events request. The origin body field is still required, but with this header its value is excluded from the event identity.
  2. Keep the eventName exactly as you sent it to /v4/track (it's case-sensitive).
  3. Do not add eventVersion — leave it out, even if the event name matches one of the documented recommended events (e.g. "viewed product", "placed order"). Adding a version changes the event identity and breaks existing automations and segments.
  4. Keep your existing properties structure as is.

v4 request:

POST /v4/track
// Headers:
//   Authorization: Bearer YOUR_ACCESS_TOKEN
{
  "eventName": "viewed product",
  "contact": {
    "email": "[email protected]"
  },
  "properties": {
    "url": "https://example.com/product/1",
    "productTitle": "Gadget",
    "productID": "prod-1"
  }
}

Latest stable request (same event, same identity):

POST /api/events
// Headers:
//   Authorization: Bearer YOUR_ACCESS_TOKEN
//   Omnisend-Version: 2026-03-15
//   compatibility: no-origin
{
  "eventName": "viewed product",
  "origin": "api",
  "contact": {
    "email": "[email protected]"
  },
  "properties": {
    "url": "https://example.com/product/1",
    "productTitle": "Gadget",
    "productID": "prod-1"
  }
}

Other changes to be aware of:

  • contact.phoneNumber is renamed to contact.phone. Other contact properties can still be passed and will create or update the contact, same as in v4.
  • origin is a required body field — use "api" (with the compatibility: no-origin header it does not affect event identity).
  • Optional new fields: eventTime (ISO 8601, defaults to now) and eventID (UUID, used for historical event deduplication).
  • The endpoint still responds with 202 Accepted and processes events asynchronously.

Adopting the recommended-event format (new events only)

The latest stable version has predefined recommended events with documented names, eventVersion values and property structures (see the events documentation). They unlock automation presets, segment templates and enhanced reporting.

Use the full recommended format — documented eventVersion, real origin, documented property structure, and no compatibility header — only for:

  • new events you didn't send through /v4/track, or
  • previously tracked events where you consciously accept that every existing automation and segment built on them must be reconfigured for the new event identity.

Do not mix modes for the same event name — pick one identity per event and stay with it.

Migration checklist

  1. Add the Omnisend-Version: 2026-03-15 header to all requests.
  2. Replace POST /v4/identify with POST /api/contacts and convert email/phoneNumber/optIns/consents to the identifiers array.
  3. Replace GET /v4/contacts/id:{id} with GET /api/contacts/{id}, and email/phone lookups with GET /api/contacts?email=... / ?phone=....
  4. Replace POST /v4/track with POST /api/events: rename contact.phoneNumber to contact.phone, add the required origin field, and for every previously tracked event add the compatibility: no-origin header, keep the same eventName and do not add eventVersion.
  5. Verify after switching: trigger one of your events and confirm the existing automation/segment still reacts to it.
  6. Update response handling: contact creation is now synchronous and returns the contact object.

If you need help migrating, contact our support team at [email protected].