Skip to main content

Legal Holds on Messaging Conversations

A legal hold (litigation hold) preserves a messaging conversation for eDiscovery. When you place a hold on a conversation, every message in that thread is exempt from the age-based retention sweep — the sweep skips both the media-null step and the hard-delete step — so the thread survives for litigation preservation, supervision review, or a regulator’s request. This page covers messaging conversations (SMS, MMS, WhatsApp, RCS, email, chat); for voice and video recordings, the same hold is exposed on the Settings → Compliance → Legal holds screen under recordings.
Preservation is your control, not ours — and this page is not legal advice. Whether a conversation must be preserved, for how long, and who must attest to it are your organization’s calls. Confirm the specifics with qualified counsel.

What a hold protects you from

Retention is designed to delete. Under Settings → Compliance → Retention, each channel has a window; once a message ages past it, the retention sweep first clears the message’s media (the media_url is nulled), then — when your retention policy applies hard delete — removes the message row entirely. That is the right posture for storage limitation in normal operation, and the wrong posture when counsel notifies you that a matter is anticipated or ongoing. A legal hold inverts that for one conversation: place the hold, and the retention sweep skips the thread for as long as the hold stands. Messages that arrive after the hold was placed are also protected — the hold belongs to the conversation, not just to the messages that existed at the moment you placed it. A held conversation is also protected against direct destruction: a hard-delete of a held conversation is refused with 409 legal_hold_active, so no operator — admin included — can destroy a preserved thread without first releasing the hold.
A hold does not change what you can read or export. Held threads remain fully visible in the dashboard and through the conversations API; the hold only stops the deletion machinery.

Place or release a hold

The hold surface is one endpoint pair per conversation. Placing and releasing require an owner or admin role; reads use the same gate. Every write is recorded in your audit log (see Audit trail).

Check the current hold state

  • legal_hold — true while the conversation is exempt from the retention sweep.
  • reason — the free-text justification recorded with the last change (up to 512 characters).
  • updated_by / updated_at — who changed the hold most recently and when.
  • message_count / held_message_count — the protected fraction of the thread, so you can confirm the hold actually propagated.
A conversation id that does not exist returns 404.

Place the hold

Send hold: true with the reason your matter-management process requires:
The response echoes the resulting state plus messages_updated — the number of existing message rows the hold was propagated to. Placing a hold is idempotent: re-sending hold: true on an already-held conversation simply re-stamps the reason and timestamp.

Release the hold

When the matter is closed and counsel confirms preservation may end, release with hold: false:
After release, the thread rejoins the normal retention sweep on the next cycle: messages already past their window become sweep-eligible again.
Release is not reversible. Once the hold is off, a message that is already older than its retention window can be media-nulled and hard-deleted by the next sweep. Confirm the matter is genuinely closed — and mirror an immutable archival export first if you still need a preserved copy — before you release.

Who can place a hold

Legal-hold reads and writes both require an owner or admin API key or dashboard role. Preservation is a compliance-governance decision, so the surface is deliberately kept off the everyday messaging operator role. A caller without the role receives 403.

Audit trail

Every place-or-release call is written to your workspace audit log with the resulting hold state, the recorded reason, and the number of messages the hold propagated to. An auditor can reconstruct the preservation chain — who held which conversation, why, when, and when it was released — from the audit log alone. The most recent reason, actor, and timestamp also stay readable on the conversation itself via the GET endpoint.

How the hold interacts with retention and erasure

  • Age-based retention sweep. Held conversations are skipped entirely — no media-null, no hard-delete — for as long as the hold stands. This includes messages that arrive after the hold was placed.
  • Admin hard-delete. Refusing to destroy a held conversation is by design: the hard-delete path returns 409 legal_hold_active until the hold is released. Use the reversible archive option if you merely want the thread out of the inbox.
  • DSAR / erasure requests. A legal hold is a preservation obligation, not a defensible-disposal shortcut — GDPR erasure and litigation preservation can genuinely conflict, and that conflict is resolved by your counsel, not by the platform. Erasure workflows do not silently override a held thread; reconcile holds against open erasure requests as part of your DSAR process (see Data subject requests).
  • Immutable archival. A hold preserves the live thread in place; an archival export produces a tamper-evident copy you pin into your own WORM store. For matters likely to outlive your Orbit retention windows, run both: hold the thread, then archive it.

How the flag reaches the sweep

A messaging hold is a flag on the existing metadata JSON column — no schema change, no migration, nothing to provision before you can place a hold. The hold is stored under the key legal_hold as a JSON boolean, alongside three sibling keys the hold writes at the same time:
  • legal_hold — true while the conversation is exempt from the sweep.
  • legal_hold_reason — the free-text justification recorded with the last change (up to 512 characters).
  • legal_hold_updated_by — the user id of the operator who last changed the hold.
  • legal_hold_updated_at — the ISO timestamp of the last change.
The retention sweep treats a message as held when its metadata.legal_hold reads as the text true. A row with the key absent (JSON null) or set to false is not held and stays sweep-eligible — so a conversation that was never held, or a hold you have released, behaves exactly like every other row. The predicate is a strict equality: only an explicit true removes a thread from the swept set.

Why the flag is placed on the conversation, not each message

The retention sweep that reaps message media keys on messages.metadata per row. To exempt a whole thread it is not enough to flag the conversation row — the sweep would still reap each message’s media_url and, when your policy applies hard delete, delete each message row on age. So placing a hold does two writes in one call:
  1. Stamp legal_hold: true on the conversation row.
  2. Propagate the same flag to every messages row already in that conversation.
A message that arrives after the hold was placed is still protected, because the conversation row carries the flag and the sweep checks it — the hold belongs to the conversation, not to the messages that existed at the moment you placed it. Releasing the hold reverses both writes: the flag is set to false on the conversation and on every message row, so the thread rejoins the normal sweep on the next cycle. The sibling audit keys (legal_hold_updated_by, legal_hold_updated_at) are kept on release rather than deleted, so the row still shows who released the hold and when.

What the hold protects against

The age-based sweep runs in two steps for each message that ages past its per-channel window: first it nulls messages.media_url (clearing the stored media), then — only when your retention policy sets apply_hard_delete — it deletes the message row outright. A hold exempts the thread from both steps for as long as it stands: the media is not nulled and the row is not deleted, even when hard delete is enabled. This is the protection that matters for regulated buyers — a conversation under an active matter survives the media-nulling step and the apply_hard_delete row-removal step, not just one of them. For the per-channel windows and the apply_hard_delete toggle, see Per-Domain Data Retention Policy.

Re-applying a hold is a no-op

Placing a hold on a conversation that is already held does not duplicate or escalate the hold — it re-stamps the reason, actor, and timestamp on the conversation and every message row, and returns the resulting state. You can re-run a place-hold call as many times as your matter-management process needs (for example, to refresh the recorded reason) without changing the sweep’s behaviour. The response’s messages_updated reports the number of existing message rows the flag was propagated to in that call.

eDiscovery readiness

A legal hold delays the retention sweep; it does not assert that any particular regulation is satisfied. Whether a hold is sufficient for a given matter, how long it must stand, and which conversations it must cover are your counsel’s calls — Orbit provides the exemption, the audit trail, and the API surface, not the retention determination. Every place-or-release call is written to your workspace audit log with the resulting hold state, the recorded reason, the actor, the timestamp, and the number of message rows the hold propagated to. An auditor can reconstruct the preservation chain — which conversations were held, by whom, for what stated reason, when each was placed, and when each was released — from the audit log alone. To assemble a production-ready evidence bundle for a matter, pair the hold with an immutable archival export of the preserved thread.

Worked example: hold a conversation by id, then release it

The hold surface keys on a conversation id, not a message id — the hold applies to the whole thread. Find the conversation id in the conversation view in the dashboard or in the conversation_id field of any message in the thread. Place the hold:
The response confirms the conversation is held and reports how many existing message rows the flag reached:
When the matter closes and counsel confirms preservation may end, release with hold: false:
After release, the thread rejoins the normal retention sweep on the next cycle: messages already past their per-channel window become sweep-eligible again. If you still need a preserved copy after release, mirror an immutable archival export before you release — once the hold is off, the next sweep can media-null and (if your policy enables it) hard-delete rows that are already past their window.

Manage holds in the dashboard

Go to Settings → Compliance → Legal holds. The screen covers both preservable entity kinds side by side:
  • Messaging conversations — paste the conversation id (visible in the conversation view and in the API), read the current hold state with its held/total message tally, and place or release the hold with a reason.
  • Voice and video recordings — the equivalent hold for recording ids, exempting them from the media-deletion retention sweep.
The screen is visible to owner and admin roles only, the same gate the API enforces.

Your preservation posture stays yours

Legal holds are a tenant-owned control: Orbit provides the exemption, the audit trail, and the API surface — when preservation must start, which conversations it covers, what reason is sufficient, and when it may end are your organization’s decisions. Nothing places a hold automatically; every hold exists because an owner or admin in your workspace created it, and every one of those decisions is auditable.

Immutable Archival Export

Copy preserved records into a tamper-evident bundle for your own WORM store.

Data Subject Requests

Access and erasure workflows, and how to reconcile them with open holds.

Per-Domain Data Retention Policy

The per-channel retention windows and the hard-delete toggle a hold exempts a thread from.

Evidence Binder

Assemble audit-ready evidence bundles from the controls you configured.

SOC 2 Controls

The broader control environment around logging and access.