Vieunite.Developers
Open quickstart

Keeping content current

Keep your artwork data up to date.

Artwork can be updated or withdrawn after an editor selects it. Use Sync to refresh your local cache and stored references. Add webhooks when you have a delivery process in place and need faster updates.

Choose the right mechanism

Incremental Sync

Pull changes in order

Start here for a scheduled refresh or local search index. A saved cursor lets your job resume after a restart.

GET /v1/sync

Webhooks

Process an event delivery

Use delivered events to trigger a refresh. Verify the signature and make repeated deliveries safe to process.

/v1/webhooks/*

You can use both: delivered webhooks trigger a refresh, while Sync catches changes missed during downtime.

Incremental Sync

On the first request, omit cursor. For later requests, use the last cursor you successfully committed. Apply the page of changes and save its next_cursor in the same database transaction so a crash cannot skip unapplied events.

Resume from a saved cursor · omit cursor on the first callcURL
curl --get "https://collection-test.vieunite.com/v1/sync" \
  -H "Authorization: Bearer $VIEUNITE_TENANT_TOKEN" \
  --data-urlencode "types=artwork,collection" \
  --data-urlencode "limit=100" \
  --data-urlencode "cursor=$LAST_COMMITTED_CURSOR"
sync pageJSON
{
  "data": [
    {
      "event_id": "evt_123",
      "type": "artwork",
      "id": "art_456",
      "action": "update",
      "version": 7,
      "updated_at": "2026-07-15T16:30:43Z",
      "idempotency_key": "artwork:art_456:7",
      "links": { "resource": "/v1/artworks/art_456" }
    }
  ],
  "next_cursor": "eyJvZmZzZXQiOjEwMH0",
  "has_more": true
}

Continue while has_more is true. Once caught up, keep the committed cursor and poll again on your next scheduled run. If processing fails, retry the same page before advancing.

Apply change events

  • publish or updateResolve or fetch the current resource and replace the cached version transactionally.
  • deleteRemove it from discovery and mark stored references unavailable using the supplied reason.
  • Repeated eventDeduplicate with idempotency_key or compare the incoming resource version.
  • Processing failureDo not advance the committed cursor until the page can be replayed safely.

Register a webhook endpoint

Choose an HTTPS route on your backend that can read the raw request body. Register it below, then store the returned signing secret in your secret manager. You’ll use that secret to verify requests.

create endpointcURL
curl --request POST "https://collection-test.vieunite.com/v1/webhooks/endpoints" \
  -H "Authorization: Bearer $VIEUNITE_TENANT_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
  "url": "https://cms.example.com/webhooks/vieunite",
  "event_types": [
    "artwork.updated",
    "artwork.unpublished",
    "artwork.rights_updated",
    "artwork.asset_updated",
    "collection.updated"
  ]
}'

Verify webhook signatures

Verify the signature before parsing or processing the event. The signature is a base64 HMAC-SHA256 of <timestamp>.<raw-request-body>. The examples accept timestamps within five minutes and compare signatures in constant time.

Pass the original request bytes and a header map with lowercase names to these helpers. Configure your framework’s raw-body support before JSON middleware; serialising parsed JSON again will change the bytes and fail verification.

signature verificationJavaScript
import crypto from "node:crypto";

const MAX_AGE_SECONDS = 300;

function verifyVieuniteWebhook({ rawBody, headers, secret }) {
  if (!Buffer.isBuffer(rawBody)) {
    throw new TypeError("rawBody must be the unparsed request Buffer");
  }

  const timestamp = headers["x-vieunite-timestamp"] || "";
  const supplied = headers["x-vieunite-signature"] || "";
  if (!/^\d+$/.test(timestamp)) return false;

  const timestampSeconds = Number(timestamp);
  const age = Math.abs(Math.floor(Date.now() / 1000) - timestampSeconds);
  if (!Number.isSafeInteger(timestampSeconds) || age > MAX_AGE_SECONDS) {
    return false;
  }

  const signedBytes = Buffer.concat([
    Buffer.from(timestamp, "ascii"),
    Buffer.from(".", "ascii"),
    rawBody,
  ]);
  const digest = crypto
    .createHmac("sha256", secret)
    .update(signedBytes)
    .digest("base64");
  const expected = Buffer.from(`v1=${digest}`, "ascii");
  const received = Buffer.from(supplied, "ascii");

  return expected.length === received.length &&
    crypto.timingSafeEqual(expected, received);
}
X-Vieunite-TimestampUnix timestamp included in the signed message. X-Vieunite-Signaturev1=<base64-signature>. Idempotency-KeyStable key for deduplicating the event.

Delivery controls

The API exposes event listing and explicit delivery preparation. A dry run returns the exact payload, signature headers and eligible target without sending an HTTP request.

prepare a deliverycURL
curl --request POST "https://collection-test.vieunite.com/v1/webhooks/events/evt_123/deliver" \
  -H "Authorization: Bearer $VIEUNITE_TENANT_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
  "endpoint_id": "wep_123",
  "dry_run": true,
  "timeout_seconds": 5
}'

Only set dry_run to false when intentionally requesting a real synchronous delivery. Treat this as an advanced testing or replay operation.

Search documentation

Start with “Picker”, “rendition”, or an endpoint path.
    Case study screenshot

    Screenshot preview