Journey. As a developer, I need to know whether an audio clip —
submitted by a user, scraped from social media, or received through a
moderation queue — was generated by my deployment, and ideally *which
request* produced it, without exposing anyone's biometric data.
Every clip synthesized by the Speech service (:40161, prefix /v3) carries an invisible dual watermark, embedded automatically before encoding — there is nothing to enable per-request:
- A robust detection gate (AudioSeal) answers *"was this audio
produced here?"*. It survives MP3/AAC distribution, resampling, loudness normalization, and quiet recordings down to −20 dB, because detection peak-normalizes the audio before scoring.
- A 128-bit payload (WavMark) answers "who and which request": a
privacy-safe 4-byte HMAC hint of the voice print — the raw voice print UUID is never exposed — plus the low 64 bits of the request ID, CRC-verified.
One multipart POST to /v3/audio/provenance returns both layers.
Prerequisites
- The Speech service with watermarking enabled:
WATERMARK_ENABLED=true
and the four ONNX model paths resolvable (defaults live under services-reusable/speech-service/assets/models/; regenerate them with speech-service/wm-exporter). When the engine is off, the check returns 503 — an explicit, honest answer rather than a fake negative.
- Any audio file: WAV, MP3, AAC, … — the service conforms it to 24 kHz
mono before detection. ffmpeg on PATH is required by the service.
- Auth is the standard pair:
x-api-key+x-client-app-keyand a
user JWT (Step 1). In sandbox/lower environments any or no JWT passes.
Concepts
- The response is three fields plus confidence.
produced_by_service
(boolean gate verdict), voice_print_hint and request_id_lo (payload layer — both null when the 128-bit payload didn't survive the journey, even if the gate fired), and confidence in [0, 1], computed as 0.4 * gate_score + 0.6 * payload_bit_accuracy (so gate-only hits land in [0.4, 1); clean audio scores near 0).
- Gate and payload have different resilience. The gate survives heavy
processing (MP3 ≥ 96 kbps, telephone band-pass, −20 dB quiet recordings, EBU R128 normalization); the payload is pickier — expect it intact on lossless and light MP3, dropped on aggressive codecs. A produced_by_service: true with null payload fields is a verified but untraceable clip.
- What defeats the check, by design: tempo/speed shifts (≥ 3%),
clipping that squashes the waveform body, and cropping away most of the clip — severe nonlinear damage is not invertible, and the service says false rather than guess.
- The MCP tool
check_audio_provenanceexposes the same check to
agents (/agent/v1/mcp/speech/* SSE endpoint).
- Privacy. The voice-print hint is an HMAC-truncated 4-byte digest; it
lets your back office correlate clips with voice prints, but cannot be reversed into the voice print or the user.
Step 1 — Get a user JWT
USER_TOKEN=$(curl -sf -X POST "${AUTH_URL}/v3/auth/qa/login" \
-H "Content-Type: application/json" \
-H "x-api-key: ${INAYA_API_KEY}" \
-H "x-client-app-key: ${INAYA_APP_KEY}" \
-d "{\"device_id\": \"g62-demo\", \"locale\": \"en-US\", \"tenant_id\": \"${TENANT_ID}\", \"username\": \"g62-checker\"}" \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')
test -n "${USER_TOKEN}" && echo "JWT acquired (${#USER_TOKEN} chars)"Expected — a 24 h PS512 JWT (qa/login provisions the user on first use; device_id must be ≥ 8 chars).
Step 2 — Produce a watermarked clip
Any synthesis is automatically watermarked. If you don't have a voice print yet, register one first — the full enrollment walk-through is Cloning a voice and speaking; the short version:
export WORKDIR=$(mktemp -d /tmp/g62-prov.XXXXXX)
say -o "${WORKDIR}/voice.wav" --data-format=LEI16@24000 \
"Hello, this is my voice reference for the provenance check guide."
VP_PATH=$(curl -s -X POST "${SPEECH_URL}/v3/voice-prints" \
-H "x-api-key: ${INAYA_API_KEY}" \
-H "x-client-app-key: ${INAYA_APP_KEY}" \
-H "Authorization: Bearer ${USER_TOKEN}" \
-F "file=@${WORKDIR}/voice.wav" \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["voice_print_wav_path"])')
echo "voice print: ${VP_PATH}"
curl -s -o "${WORKDIR}/spoken.mp3" -w 'HTTP %{http_code} | %{size_download} bytes\n' \
-X POST "${SPEECH_URL}/v3/text-to-speech" \
-H "Content-Type: application/json" \
-H "x-api-key: ${INAYA_API_KEY}" \
-H "x-client-app-key: ${INAYA_APP_KEY}" \
-H "Authorization: Bearer ${USER_TOKEN}" \
-d "{\"input\": \"This sentence carries an invisible watermark.\", \"reference_voiceprint_path\": \"${VP_PATH}\", \"locale\": \"en\", \"output_format\": \"wav\"}"
file "${WORKDIR}/spoken.mp3" | grep -qi audio && echo "synthesized clip ready (watermarked at synthesis time)"Expected — 200 with a WAV body. Synthesis is the slow call (~20–45 s warm; a first-call-after-boot can hit the internal deadline and return 504 tts_synthesis_failed — retry). The watermark is embedded before encoding — re-encoding the result to MP3 yourself does not remove it.
Re-speak output verifies identically.POST /v3/respeak
(multipartpayloadJSON +fileaudio) re-renders a recording in the
registered voice — transcribe → optional enhancement → synthesize — and
watermarks the result on the same path. Verified live: a re-spoken clip
returnsproduced_by_service: truewithconfidence: 1.0, the same
voice-print hint as TTS output for that voice print, and a freshrequest_id_loper request.
Step 3 — Check the synthesized clip (positive verdict)
curl -s -X POST "${SPEECH_URL}/v3/audio/provenance" \
-H "x-api-key: ${INAYA_API_KEY}" \
-H "x-client-app-key: ${INAYA_APP_KEY}" \
-H "Authorization: Bearer ${USER_TOKEN}" \
-F "audio=@${WORKDIR}/spoken.mp3" \
-o "${WORKDIR}/verdict.json"
python3 - "${WORKDIR}/verdict.json" <<'PY'
import json, sys
d = json.load(open(sys.argv[1]))
assert d["produced_by_service"] is True, d
assert d["voice_print_hint"], "payload fields should be present on a fresh WAV"
assert d["request_id_lo"] is not None, d
print(f'produced_by_service={d["produced_by_service"]} confidence={d["confidence"]:.4f} hint={d["voice_print_hint"]} request_id_lo={d["request_id_lo"]}')
PYExpected response — 200: {"produced_by_service":true,"request_id_lo":<u64>,"voice_print_hint":"<8 hex chars>","confidence":~0.99}. Detection takes a few seconds (decode + two ONNX model families). The multipart field must be audio (or file); a missing field → 400.
Step 4 — Check audio the platform did not produce (negative verdict)
say -o "${WORKDIR}/not-ours.wav" --data-format=LEI16@24000 \
"This recording was never touched by the speech service."
curl -s -X POST "${SPEECH_URL}/v3/audio/provenance" \
-H "x-api-key: ${INAYA_API_KEY}" \
-H "x-client-app-key: ${INAYA_APP_KEY}" \
-H "Authorization: Bearer ${USER_TOKEN}" \
-F "audio=@${WORKDIR}/not-ours.wav" \
-o "${WORKDIR}/verdict-negative.json"
python3 - "${WORKDIR}/verdict-negative.json" <<'PY'
import json, sys
d = json.load(open(sys.argv[1]))
assert d["produced_by_service"] is False, d
assert d["voice_print_hint"] is None and d["request_id_lo"] is None, d
print(f'produced_by_service={d["produced_by_service"]} confidence={d["confidence"]:.4f}')
PYExpected response — 200 with produced_by_service:false, payload fields null, and confidence near 0 (typically < 0.05). Note the check is a positive proof only: false means "no platform watermark detected", not "this audio is authentic human speech".
Step 5 — What survives a realistic distribution chain
Re-encode and re-check the same clip — the gate holds even where the payload starts dropping:
ffmpeg -y -loglevel error -i "${WORKDIR}/spoken.mp3" -codec:a libmp3lame -b:a 128k "${WORKDIR}/spoken-128k.mp3"
ffmpeg -y -loglevel error -i "${WORKDIR}/spoken.mp3" -af volume=-14dB "${WORKDIR}/spoken-quiet.wav"
for f in spoken-128k.mp3 spoken-quiet.wav; do
echo -n "${f}: "
curl -s -X POST "${SPEECH_URL}/v3/audio/provenance" \
-H "x-api-key: ${INAYA_API_KEY}" \
-H "x-client-app-key: ${INAYA_APP_KEY}" \
-H "Authorization: Bearer ${USER_TOKEN}" \
-F "audio=@${WORKDIR}/${f}" \
-o "${WORKDIR}/verdict-${f}.json"
python3 - "${WORKDIR}/verdict-${f}.json" <<'PY'
import json, sys
d = json.load(open(sys.argv[1]))
print(f'produced_by_service={d["produced_by_service"]} confidence={d["confidence"]:.4f} hint={d["voice_print_hint"]}')
PY
doneExpected — both stay produced_by_service:true: the 128 kbps MP3 typically keeps the full payload (hint present), while the −14 dB file detects via the normalized gate — the quiet-recording scenario that peak-normalization in the detect path exists for.
Where to go next
enrollment and synthesis journey this guide checks.
- Transforming media — re-encode, resample, and
denoise chains for your media pipeline.
- API reference — Swagger UI at
/v3/swagger-ui/on the Speech service,
and the check_audio_provenance tool in the service's MCP surface.