Plan with AI

Get started →
Guides
Core Concepts5 min read

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_id field 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:

  1. Open the resource's create operation and look for an external_id request field.
  2. Look for an explicit by-external-id route on that resource.
  3. Check which methods exist on that route. A GET route does not imply PUT or DELETE, and the reverse is also true.
  4. Check the accepted type. The current API accepts strings for some resources and integers for others.
  5. 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:

  1. Derive the same deterministic external ID every time you process the source record.
  2. Look up the Workvivo resource by that external ID.
  3. If it exists, compare or merge the desired state and send the resource's full PUT body.
  4. If the endpoint's documented missing-resource response is returned, create the resource with POST and include the external ID.
  5. 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-Id on 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.