External IDs & Syncing
An external ID is the stable identifier that a record already has in your source system. When a Workvivo resource supports it, you can store that identifier with the Workvivo record and use it in later API requests.
Without an external ID, an integration usually needs its own mapping table:
source record 8421 -> Workvivo record 24
With a supported external ID, the source identifier becomes the lookup key:
source record 8421 -> external_id "source-update-8421"
This removes the need for a separate canonical ID mapping. You can still record Workvivo IDs in logs or caches, but the source ID remains the durable key used to reconcile both systems.
External IDs are not universal. Support, accepted data type, and available operations vary by resource. An
external_idfield in one response does not prove that another resource accepts one, and a create field does not prove that a lookup route exists.
Before you use an external ID
Create a static API key under API Keys & JWT Settings in Workvivo administration and send it as a Bearer token. It is not an OAuth 2.0 token or a personal user-login token. Every request also needs the Workvivo-Id header containing the organisation identifier shown alongside the Base URL.
The Workvivo-Id selects the organisation in which the external ID is resolved. Design each external ID to be stable and unique within that organisation and resource type.
Workvivo does not provide a sandbox or staging environment. External-ID writes reach production, just like every other API write, so establish the identifier scheme before creating records.
Check support for the exact resource
Use the API reference to check each operation you need:
- Open the resource's create operation and look for an
external_idrequest field. - Look for an explicit
by-external-idroute on that resource. - Check which methods exist on that route. A
GETroute does not implyPUTorDELETE, and the reverse is also true. - Check the accepted type. The current API accepts strings for some resources and integers for others.
- For nested resources, check both the parent and child identifiers. A related user external ID does not give the parent resource its own external ID.
The current v1 API does not provide a generic ?external_id= lookup that works across collections. Use only the external-ID path or operation-specific query parameter documented for the selected endpoint.
Direct external-ID support
This table lists resources that can use their own external ID in at least one current v1 operation.
| Resource | Set on create | Direct lookup | Write using the resource's external ID | Accepted external ID |
|---|---|---|---|---|
| Users | Not through the Customer API | GET /v1/users/by-external-id/{external_id} |
Profile photos and badge assignments can address the user by external ID; there is no general user update-by-external-ID route | String |
| Teams | POST /v1/teams |
GET /v1/teams/by-external-id/{external_id} |
PATCH /v1/teams/by-external-id/{external_id}/users changes membership; there is no general team update or delete route |
String |
| Articles | POST /v1/articles |
GET /v1/articles/by-external-id/{external_id} |
PUT and DELETE on the same path, plus nested language variants, images, reactions, comments, and acknowledgements |
Integer |
| Events | POST /v1/events |
GET /v1/events/by-external-id/{external_id} |
PUT and DELETE on the same path, plus nested images, attendees, reactions, and comments |
Integer |
| Updates | POST /v1/updates |
GET /v1/updates/by-external-id/{external_id} |
PUT and DELETE on the same path, plus nested reactions, comments, acknowledgements, and campaigns |
String |
| Kudos | POST /v1/kudos |
GET /v1/kudos/by-external-id/{external_id} |
PUT and DELETE on the same path, plus nested reactions, comments, acknowledgements, and campaigns |
String |
| Pages | POST /v1/pages |
No direct lookup-by-external-ID route | PUT and DELETE through /v1/pages/by-external-id/{external_id}, plus language variants |
Integer |
| Comments on articles, events, updates, and kudos | Set comment_external_id when creating the nested comment |
Nested GET routes are available when both the parent and comment are addressed by external ID |
Nested PUT and DELETE routes use the parent external ID and comment_external_id |
String for the comment; the parent keeps its own type |
For articles, events, and pages, supply a whole-number external ID. Users, teams, updates, kudos, and comments accept string identifiers. Recheck the exact operation before reusing an identifier format from another resource.
Pages are an important partial-support case: you can create, update, and delete a page with an external ID, but there is no GET /v1/pages/by-external-id/{external_id} route. Teams are another partial case: they support creation, lookup, and membership changes by external ID, but not a general update or delete.
Related external-ID support
Some operations accept a user's external ID without giving the containing resource its own external identity:
| Area | What the external ID identifies |
|---|---|
| Frontline status | Each user in PUT /v1/users/frontline-status |
| Spaces | A user_external_id filter and users in membership or role changes; the space itself still uses its Workvivo ID |
| Team membership | Users to add or remove, while the team can be addressed by either its Workvivo ID or its external ID |
| Content actions | The creator, commenter, liker, reactor, or reporting user on supported article, event, update, and kudos operations |
| Event attendees | The attendee user; the event can also use its own external ID |
| Notifications | Users in an external-user audience |
| Discount code users | The assigned user; the discount code still uses its Workvivo ID |
| Chat channels | Users and members; channels use their channel URL rather than a channel external ID |
| Collection filters | user_external_id, user_external_ids, or creator_external_id on the specific spaces, chat, activities, updates, or kudos operation that documents it |
Do not treat these related-user fields as proof that spaces, notifications, discount codes, or chat channels have their own external-ID lookup.
Resources without their own external-ID route
The remaining top-level resources in the current API reference do not expose lookup or writes using an external ID belonging to that resource: roles, spaces and default spaces, notifications, SIEM events, activities, goals, badges, billboards, mega menu entries, discount codes, chat bots and channels, documents, organisation reaction types, apps, livestreams, webhooks, and Unwired operations.
Nested resources such as likes, reactions, acknowledgements, campaigns, attendees, badge users, and discount-code users also do not gain an independent external ID. Some can be reached through a supported parent's external-ID route or can accept a related user's external ID, as listed above.
Look up a resource by external ID
Use the resource-specific route and URL-encode the external ID before inserting it into a path. This example retrieves an update using the stable key from its source system.
curl -X GET https://api.workvivo.com/v1/updates/by-external-id/source-update-8421 \
-H "Authorization: Bearer $WORKVIVO_TOKEN" \
-H "Workvivo-Id: $WORKVIVO_ORG_ID" \
-H "Accept: application/json"
const token = process.env.WORKVIVO_TOKEN;
const organisationId = process.env.WORKVIVO_ORG_ID;
const externalId = 'source-update-8421';
if (!token || !organisationId) {
throw new Error(
'Set WORKVIVO_TOKEN and WORKVIVO_ORG_ID before running this example.',
);
}
const response = await fetch(
`https://api.workvivo.com/v1/updates/by-external-id/${encodeURIComponent(externalId)}`,
{
headers: {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
'Workvivo-Id': organisationId,
},
},
);
const body = await response.json();
if (!response.ok) {
throw new Error(
`Update lookup failed (${response.status}): ${JSON.stringify(body)}`,
);
}
console.log(body.data);
Do not fall back to listing every resource and searching locally when a direct external-ID lookup exists. The detail route is both clearer and less work.
Write a resource with an external ID
On creation, send the source key in the resource's documented external_id field. The Updates API uses multipart form data and requires the content, creator, and creation date as well as the optional external ID.
curl -X POST https://api.workvivo.com/v1/updates \
-H "Authorization: Bearer $WORKVIVO_TOKEN" \
-H "Workvivo-Id: $WORKVIVO_ORG_ID" \
-H "Accept: application/json" \
-F "external_id=source-update-8421" \
-F "user_external_id=employee-1001" \
--form-string "text=The source record has been published." \
-F "created_at=2026-07-25T12:00:00Z"
After creation, address the existing record through its external-ID route. A PUT is a full resource update: send every required field and any optional state that must be preserved.
curl -X PUT https://api.workvivo.com/v1/updates/by-external-id/source-update-8421 \
-H "Authorization: Bearer $WORKVIVO_TOKEN" \
-H "Workvivo-Id: $WORKVIVO_ORG_ID" \
-H "Accept: application/json" \
-F "user_external_id=employee-1001" \
--form-string "text=The source record has been published and reviewed." \
-F "created_at=2026-07-25T12:00:00Z"
The external ID in the path selects the record. It does not rename the external ID, and PUT does not create a missing update.
Use external IDs for practical idempotency
Workvivo has no native idempotency-key mechanism and no atomic upsert endpoint. Do not send an Idempotency-Key header or assume that repeating a POST returns the first response.
For resources with create, lookup, and update support, implement upsert behavior in your integration:
- Derive the same deterministic external ID every time you process the source record.
- Look up the Workvivo resource by that external ID.
- If it exists, compare or merge the desired state and send the resource's full
PUTbody. - If the endpoint's documented missing-resource response is returned, create the resource with
POSTand include the external ID. - If the create response is lost or the create fails because another worker may have won the race, look up the external ID again before deciding whether to retry.
This makes repeated sync runs converge on one resource rather than creating a new resource on every run. It is practical idempotency implemented by your integration, not a guarantee attached to one HTTP request.
Serialize work for the same combination of organisation, resource type, and external ID. A lookup followed by a create is not atomic, so two workers can both observe a missing record. If concurrent processing cannot be avoided, treat a duplicate-create failure as a signal to read again and then update the record that now exists.
Among top-level resources, only articles, events, updates, and kudos currently have the complete create + direct lookup + update route set for this general pattern. Nested comments can use the pattern when their supported parent also has an external ID, but comments have no standalone route. Pages lack direct lookup, teams lack a general resource update, and users lack Customer API creation and general update routes. Adapt the workflow only when the exact resource exposes every operation you need.
Choose external IDs that stay reliable
Keep the ID stable
Use an immutable source-system key, not a display name, email address, title, or other value that people can edit. Treat the external ID as permanent after creation.
External-ID update routes use the path value to find the existing record; they are not rename operations. Some resource update implementations also ignore changes to external_id. If a source system must replace its key, plan an explicit migration and reconcile the old Workvivo record before switching. Otherwise the next sync can create a duplicate or stop finding the original.
Do not reuse one namespace for unrelated sources
External IDs are resolved inside the organisation and resource selected by the request. Two integrations that independently generate 8421 for the same resource can target the wrong record even though each source considers its own value unique.
For resources that accept strings, include a stable source and entity namespace, for example:
hris:user:8421
cms:update:8421
Articles, events, and pages currently require integer external IDs, so a string namespace is not available. Use a stable native integer key or a deliberately partitioned, collision-free integer range. Do not truncate or hash a string identifier unless your system can detect and prevent collisions.
Workvivo itself has no test environment. Keep test fixtures and non-production source jobs from sharing the live production namespace, and never point an experimental writer at production records identified by the same keys as the real integration.
Preserve the external ID in operational records
Include the external ID, resource type, operation, response status, and timestamp in integration logs. Never log the API key. This gives you enough context to reconcile an ambiguous write without turning the log into another canonical ID-mapping database.
Syncing checklist
- Confirm that the exact resource and operation support external IDs.
- Confirm whether the value must be a string or integer.
- Use a stable source key and a collision-free namespace.
- URL-encode external IDs placed in paths.
- Send the static API key as a Bearer token and include
Workvivo-Idon every request. - Treat
PUT .../by-external-id/...as update-only unless the API reference explicitly says otherwise. - Read after an ambiguous write before retrying a create.
- Serialize concurrent work for the same external ID.
- Treat external IDs as immutable after creation.
- Use Workvivo IDs for resources and operations that do not document external-ID support.