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 endpoint | Latest stable replacement |
|---|---|
POST /v4/identify | POST /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/track | POST /api/events (Events API) |
Key differences that apply to all requests:
| v4 | Latest stable | |
|---|---|---|
| Base URL | https://api.omnisend.com/v4/... | https://api.omnisend.com/api/... |
| Versioning | In the URL path (/v4/) | Omnisend-Version: 2026-03-15 header (required) |
| Authentication | OAuth 2.0 Bearer token only | OAuth 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-15See 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
POST /v4/identifyUse 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 property | Latest stable |
|---|---|
email | identifiers[] entry with "type": "email", value in id |
phoneNumber | identifiers[] entry with "type": "phone", value in id |
optIns / optOuts | identifiers[].channels.{channel}.status: subscribed, unsubscribed or nonSubscribed |
consents | identifiers[].consent object (no channel field — consent belongs to the identifier) |
firstName, lastName, gender, birthdate, address, city, state, country, countryCode, postalCode, tags, customProperties | Unchanged |
Behavioral differences:
- v4 responded with
202 Acceptedand processed the contact asynchronously. The latest stable endpoint is synchronous — it returns200/201with the stored contact (including itsid). - The new endpoint supports
sendWelcomeMessageper identifier (defaults totrue). - For partial updates of an existing contact, you can also use
PATCH /api/contacts/{id}orPATCH /api/contacts?email={email}.
Migrating GET /v4/contacts/{property}:{identifier}
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-15Note 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
POST /v4/trackUse POST /api/events.
Important: keep your automations and segments workingOmnisend identifies each event internally by its name, origin and version. Events sent via
/v4/trackcarried 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/eventswith anoriginoreventVersionincluded 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:
- Pass the
compatibility: no-originheader with every/api/eventsrequest. Theoriginbody field is still required, but with this header its value is excluded from the event identity. - Keep the
eventNameexactly as you sent it to/v4/track(it's case-sensitive). - 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. - Keep your existing
propertiesstructure 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.phoneNumberis renamed tocontact.phone. Other contact properties can still be passed and will create or update the contact, same as in v4.originis a required body field — use"api"(with thecompatibility: no-originheader it does not affect event identity).- Optional new fields:
eventTime(ISO 8601, defaults to now) andeventID(UUID, used for historical event deduplication). - The endpoint still responds with
202 Acceptedand 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
- Add the
Omnisend-Version: 2026-03-15header to all requests. - Replace
POST /v4/identifywithPOST /api/contactsand convertemail/phoneNumber/optIns/consentsto theidentifiersarray. - Replace
GET /v4/contacts/id:{id}withGET /api/contacts/{id}, and email/phone lookups withGET /api/contacts?email=.../?phone=.... - Replace
POST /v4/trackwithPOST /api/events: renamecontact.phoneNumbertocontact.phone, add the requiredoriginfield, and for every previously tracked event add thecompatibility: no-originheader, keep the sameeventNameand do not addeventVersion. - Verify after switching: trigger one of your events and confirm the existing automation/segment still reacts to it.
- 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].