Skip to main content

Import conversations from signed IETF vCon containers

The export guide covers the outbound direction: turning one of your conversations into a signed vCon container. This guide covers the reverse direction. A carrier, an external AI system, or a compliance archive that received (or produced) a signed vCon can hand the artifact back to Orbit, and the envelope becomes a real thread in your omnichannel inbox — parties, per-turn dialog, and subject preserved exactly as the signer bound them. Import is signature-gated. The artifact is verified against your tenant’s shared export secret before any row is written, so a tampered or unsigned container never touches the workspace.

The signed envelope

The request body is the vCon container itself. Orbit accepts the "0.3.0" draft track of the IETF vCon container — the same shape GET /api/v1/conversations/:id/vcon exports.
Every field at the top level is optional except uuid:

Request and response

Send the container as the JSON body of a single POST. The request needs the conversations:write scope and an API key from a workspace member with write permission:
A verified import returns 201 Created:
The response tells you the new conversation id, echoes the artifact’s uuid, and reports how many dialog turns were written. The conversation is created in open status and appears in your inbox immediately. Every import is recorded in your audit log with the artifact uuid and turn count — never with the party addresses themselves.

Signature verification

Import re-runs the same verification the export guide documents, with your tenant’s shared export secret, before anything is stored:
  1. The first signatures block must declare protected.alg: "Orbit-HS256" — a container signed with any other algorithm label is rejected as invalid. There is no cross-alg acceptance.
  2. The HMAC-SHA256 over the payload_b64 string, keyed with the shared secret, must equal the block’s signature byte for byte.
  3. The decoded signed payload’s uuid must equal the container’s top-level uuid, which defeats substituting a different validly-signed payload onto the same envelope.
All three conditions, or the request fails. A stripped signature array, a signature that does not recompute, or a payload whose uuid was swapped all land in the same place: 422 with VCON_SIGNATURE_INVALID, and nothing is written. Because verification is symmetric (your tenant’s HMAC secret), only parties you handed the secret to can produce an importable artifact. If you rotate the platform signing secret, artifacts signed under the previous value stop verifying — re-export from the origin workspace, or have your counterparty re-sign with the current secret, before re-importing. Verifying by payload_b64 recompute (rather than by indexing signatures per artifact uuid) also means there is no key id to pin: the current secret either recomputes the signature or the artifact is rejected. Verification never throws on garbage input — unsigned, malformed, and tampered artifacts all return the same clean 422. An artifact that passes verification is then written with the exact bytes the signature bound; the import side applies no PII re-scan of its own, because re-scrubbing after signing would silently forge the signature.

Identity and dedupe semantics

Import is a provenance-preserving handoff, not a live inbound message — so it never folds into an existing thread:
  • Every import mints a fresh conversation id. The importer does not run the inbox’s find-or-create continuity resolver, which would otherwise fold a phone/email match onto an open thread. The artifact’s parties, timestamps, and turns land on a new row, byte-identical to the artifact.
  • Re-importing the same artifact creates a second thread. There is no uuid-level idempotency by design — two imports of one artifact are two provenance-distinct handoffs. Each attempt is recorded separately in your audit log with the same artifact uuid, so duplicate imports are observable if you need to detect them.
  • The artifact uuid is not the conversation id. The response’s artifact_uuid identifies the envelope you sent; conversation_id is the new workspace row. Track the pair (uuid, created_at) as the artifact’s identity.

Party resolution

The importer projects the artifact’s parties onto the new conversation’s contact fields:
  • Party 0 is the customer. Its tel and mailto become the conversation’s contact phone and email, and its name the contact name. When the artifact carries no explicit party: 0 entry, the first party in array order is treated as the customer.
  • Party 1 is the agent side. Its name is echoed on the import audit row. When no party: 1 exists, the first party after the customer is used.
  • Null-safe parties. A party object may carry any subset of tel, mailto, and name; blank strings are dropped. An artifact with an empty parties array still imports — the conversation’s contact fields stay unset and the subject becomes its only label.
  • Channel recovery. The importer reads the channel label from the first dialog turn’s meta.channel and accepts only the workspace channel vocabulary — sms, whatsapp, email, instagram, messenger, rcs, viber, line, telegram, voice, video, web_chat, apple_messages, wechat, kakao, zalo. Any other value collapses to the neutral sms default, so an artifact cannot write an arbitrary channel label into a real row.

Errors to plan for

Signature failures are the most common first-integration error. If every import returns VCON_SIGNATURE_INVALID, confirm the artifact’s protected.alg is exactly Orbit-HS256 and that the artifact was signed with the workspace’s current secret — a rotated secret invalidates every older artifact.

Fan-out guardrails

  • 500-turn cap. Import accepts at most 500 dialog items per request — the same bound the export side applies to a transcript. Longer histories need multiple artifacts.
  • Batched writes. The conversation row and every dialog turn land in one database transaction, with turns inserted in batches. A malformed turn rolls the entire import back atomically — no half-imported threads ever survive.
  • No outbound traffic of any kind. Import writes rows in your workspace and nothing else; it issues no SMS, voice, or provider call. The imported thread starts its inbox life in open status and picks up normal assignment and notification behavior from there.
  • Rate limits. The route shares the standard write-rate budget. For bulk migrations, pace your imports and rely on the audit log (one row per attempt, keyed by artifact uuid) to reconcile.

Worked examples

Migration flows that use import

  • Carrier handoff — the receiving carrier exports a thread as a signed vCon and provisions your shared secret out of band; the artifact verifies on import and lands as a live thread.
  • External AI system — a vCon-aware tool returns an annotated container; importing it back keeps the original parties and turn order intact on a new, provenance-distinct thread.
  • Compliance archive restore — an archived export can be brought back as an active thread when a dispute or audit reopens; the signature proves the restored record is byte-identical to the archived one.

Limits to plan for

  • Two-party shape. Party 0 resolves to the customer, party 1 to the agent side. The party_history extension for multi-party containers is not used on import.
  • Attachments and analysis are carried, not written. They survive the round-trip for fidelity but are not materialized into workspace rows.
  • No uuid-level idempotency. Re-imports mint new threads deliberately; detect duplicates via the audit log’s artifact uuid when your flow requires it.
  • Version track. Imports follow the "0.3.0" draft track of the IETF container; expect the field to move forward as the draft matures.

See also