livqenoDocs

BackendIdempotency

Idempotency

Retry a request safely without doing the work twice.

This page is for your backend (and, for chat, your app). It explains how to retry a request without doing the work twice.

Why you need it. Networks fail in an awkward way: sometimes the request reaches Livqeno and the work is done, but the response is lost on the way back. Your code sees a timeout and can't tell which case happened:

Your backend
Livqeno
request
token created
response lost
Did it work? Should I retry?
The request succeeded, but your code can't tell.

If you simply retry, you might create a second token, or post the same chat message twice. Idempotency (doing the same thing twice has the same effect as doing it once) solves this: you tag the operation with an id, and if Livqeno sees that id again, it returns the first result instead of doing the work again.

Livqeno has two ways to do this:

You're retrying…Use
A REST call (for example, minting a token with curl or your own HTTP client)The Idempotency-Key header
A chat message (from any SDK, or over the WebSocket)The clientMessageId field

Over HTTP: the Idempotency-Key header

When you call the REST API directly from your backend, add an Idempotency-Key header to the request:

Terminal
curl -X POST "$LIVQENO_API_URL/v1/rooms/$ROOM_ID/rtc-tokens" \
  -H "Authorization: Bearer $LIVQENO_API_KEY" \
  -H "Idempotency-Key: 3f8a1c22-your-own-uuid" \
  -H 'Content-Type: application/json' \
  -d '{"participantIdentity":"user-42","permissions":{"join":true,"subscribe":true}}'

If you retry with the same key, you get the original response back. No second token is made.

How to use it:

  1. Make a new UUID for each operation.
  2. Reuse that UUID only when retrying the same operation.

If two requests with the same key arrive at the same time, only one runs. The other waits up to 2 seconds for the first one's response and returns it. If the first one is still running after that, the other gets 409 RAVEN_IDEMPOTENCY_KEY_IN_USE with retryAfterSeconds. Retry with the same key and you get the first response.

Reusing a key with a different body returns 409 RAVEN_CONFLICT.

The endpoint pages show which routes support it.

How long a replay lasts

EndpointReplay window
Most idempotent endpoints24 hours
RTC token minting5 minutes

Why so short for tokens? An RTC token can expire in as little as 30 seconds. Replaying a cached token an hour later would give you a dead credential. With 5 minutes, a retry either gets a still-useful token or a fresh one.

Chat: clientMessageId

Chat messages use a field, not a header. That's because messages also go over the WebSocket, which has no headers.

This is the case you'll hit most often: a user on a weak mobile connection taps Send, the network drops, and your app retries. With the same clientMessageId, the other people in the chat see the message once, not twice. Make the id once when the user taps Send, and reuse it for every retry of that message. In your app (or backend):

await chat.sendMessage({
  text: 'Hello everyone',
  clientMessageId: crypto.randomUUID(),
});

A message counts as a duplicate when the conversation, the sender, and the clientMessageId all match. The response tells you if it was a replay:

const result = await chat.sendMessage({ text: 'Hi', clientMessageId: id });
// result.deduplicated === true  → this was a replay, not a new message

Over HTTP, a replayed message returns RAVEN_MESSAGE_ALREADY_EXISTS.

What isn't idempotent

Deletes and state changes (like ending a stream) don't use idempotency keys. They don't need them. They're already safe to retry:

  • Ending a stream that already ended returns RAVEN_STREAM_INVALID_STATE.
  • Deleting a room that's already CLOSED does nothing.

Next steps

  • Conventions: the other rules every REST call follows (errors, request ids, pagination).
  • Messages: sending, editing, and deleting chat messages.
  • Errors: what each error code means.