Docs Send sipnab's vCons to vcon.store

Send sipnab's vCons to vcon.store

Forward sipnab's vCons to the hosted vcon.store with sipnab --vcon-forward: the choice of whether to --redact them first, which the store refuses today, the compat mode its validator needs, and what the store's signature and consent fields do and do not mean.

On this page

vcon.store is a hosted store for vCons. This page sends the containers sipnab writes to it with sipnab --vcon-forward, the forwarder that Deliver the spool to a store describes.

Status on 2026-10-07: sipnab’s containers work only through a compat mode, and only when they carry audio. vcon.store refuses extensions in the form both vCon drafts define, so a sipnab container sent unchanged draws 400. --vcon-forward-compat vcon-store sends a copy the store accepts, and leaves the container on disk unchanged. Even with it, the store refuses a container whose Dialog Object has no type, which is what sipnab writes for a call whose container carries no audio: the run kept none, or --redact withheld it. The measurements are the evidence.

Before you send anything

A capture carries personal data: telephone numbers, addresses, the headers your platform sets, and, with --retain-audio, the call audio. vcon.store is another party’s system. Decide what may leave the machine first:

  • --redact replaces identities, addresses and correlation identifiers with keyed tokens, and withholds the audio. It works on a capture file (-I), not on a live capture.
  • A redacted container carries no audio, so its Dialog Object has no type, and vcon.store refuses it. The compat mode refuses it before sending, with that reason. Today the only sipnab container vcon.store accepts is one exported without --redact and with audio: the call audio and the identifiers the signaling carried, as captured.
  • What may you conclude lists what a container carries.

1. Write the containers

Export the calls from a capture file with their audio. --retain-audio makes sipnab type each call’s Dialog Object recording, with the parties, which the store requires. This container is not redacted: it carries the audio and the identifiers as captured.

sipnab -N --no-cli-print -I calls.pcap --retain-audio \
  --export-vcon-when "state == 'Completed'" --export-vcon-dir spool

With --redact added, the same command writes containers without audio, and step 3 moves each one to spool/failed with the reason.

sipnab prints Wrote N vCon container(s) to 'spool'.

2. Put the token in a file

vcon.store authenticates with a bearer token. Put it in a file only you can read. The forwarder refuses one that other users can read:

# Run all of these, in order.
umask 077
printf 'Authorization: Bearer %s\n' "$VCON_STORE_TOKEN" > vcon-store.auth

$VCON_STORE_TOKEN is the token vcon.store issued you. The forwarder never prints it, and removes it from any answer it keeps.

3. Send them

# Run all of these, in order.
sipnab --vcon-forward spool --vcon-forward-url https://api.vcon.store/v1/vcons \
  --vcon-forward-auth-file vcon-store.auth --vcon-forward-compat vcon-store \
  --vcon-forward-once
echo "exit $?"

For each container the forwarder logs one line naming the change it made to the copy it sent, then a delivered line with the store’s status, and moves the file to spool/delivered. The exit status is 0 when the store accepted every container. A container the store or the compat mode refused is in spool/failed, beside a .error.json record that says why. A 401 or 403 is different: it refuses the token or the client for every container, so the forwarder stops, moves nothing, logs the status and the start of the answer, and exits 3.

These commands ran on 2026-10-07 against a local stand-in that answers as the maintainer measured vcon.store answering, not against vcon.store itself. Without --vcon-forward-compat vcon-store the stand-in answered 400 extensions: Expected object, received array, as the store does, and the forwarder moved the container to spool/failed.

What the compat mode changes, and why

--vcon-forward-compat vcon-store changes the copy the forwarder SENDS. The file in the spool, and the copy in spool/delivered, keep the form the drafts define. The forwarder logs each change, one line per container.

In the containerSent to vcon.storeWhy
"extensions": ["sip-signaling", "CC"]"extensions": {"sip-signaling": true, "CC": true}Section 4.1.3 of both draft-ietf-vcon-vcon-core-02 and -03 defines extensions as an array of strings. vcon.store refuses that and accepts an object. The copy keeps every name, in its order.
a Dialog Object with no type or no partiesnothing: the container is not sentvcon.store requires both on every Dialog Object, as draft-ietf-vcon-vcon-core-02 did. sipnab writes a Dialog Object with neither when the container carries no audio, which section 4.3 of draft-ietf-vcon-vcon-core-03 allows. The forwarder does not invent a type sipnab did not observe, and does not drop the object, which would leave other indexes pointing at nothing. It moves the container to spool/failed with a reason that names --retain-audio and --redact.

The forwarder sends every other byte of the container as sipnab wrote it. You can drop the mode once vcon.store accepts extensions as the array of strings the drafts define.

The measurements

The maintainer measured these against vcon.store’s API on 2026-10-07. This page’s commands did not contact the store.

Sentvcon.store answered
POST /v1/vcons with "extensions": ["sip-signaling", "CC"]400, extensions: Expected object, received array
the same with extensions as an object of names mapped to true201, stored as sent
a vCon with no audio, whose Dialog Object is {}400: the store requires type and parties
the same with extensions removed201
GET of a stored vConthe content unchanged, inside a _meta envelope
DELETE of a stored vCon200, and a GET after it 404
a request with Python’s default HTTP User-Agent403, error code: 1010, from the Cloudflare front. The front accepted a curl User-Agent. The forwarder sends User-Agent: sipnab/<version>

Not measured: whether vcon.store answers 409 for a uuid it already holds, or accepts a PUT to /v1/vcons/{uuid}. --vcon-forward-replace-url exists for a store that does, and this page does not use it. Also not measured: whether the store accepts a recording Dialog Object with no body, which is what step 1 writes under --redact.

What the store adds, and what it does not mean

On each create the store wrote one entry to its transparency log (SCITT): the event vcon_created, a SHA-256 hash of the payload, a COSE signed statement and a receipt. Each stored vCon also comes back with a _meta envelope that says form: unsigned, consentStatus: unknown and retentionAction: redact.

  • The signature is the store’s, not sipnab’s. It says what vcon.store received. The container it covers is an observation from a tap, which sipnab never signs, so the signature does not make the container a recording.
  • consentStatus: unknown is accurate. sipnab records no consent, and a container carries none.

When something does not work

  • Every container lands in spool/failed with status 400 and extensions: Expected object, received array. Add --vcon-forward-compat vcon-store, move the files from spool/failed back to spool, and run the forwarder again.
  • A container lands in spool/failed with a reason about type and parties. The container carries no audio: the run kept none for that call, or the export ran with --redact. Export it again with --retain-audio and without --redact.
  • The forwarder exits 3 and logs 403 with error code: 1010. The Cloudflare front refused the request’s client. The forwarder sends User-Agent: sipnab/<version>. Check that nothing between it and the store replaces that header. It moved no file, so run it again once you fix that.
  • The forwarder exits 3 and logs 401. The token in vcon-store.auth is wrong or expired. Every container is still in spool.