Rox
Register · Developers
PUBLIC DOCS
v1 · api.roxregister.com
Sign in to Platform
OverviewQuickstartsGuidesSDKsAPI referenceErrorsWebhooksJWS vectors
Quickstarts
The three calls that matter, with snippets that match the live contract exactly, the signing paths below are round-trip-verified against the published JWS test vectors. You need a vault API key and a registered request-signing key, both created in your Platform.
1 · Seal a digest
POST /v1/seal
Hash the document locally, then sign the request with your registered key. The JWS covers the canonical string RRv1\n{method}\n{path}\n{sha256_hex(body)}\n{ts}\n{nonce}, Rox cannot anchor in your name without it. The request is validated and either rejected immediately or accepted with 202; exactly one terminal outcome, anchored or failed, follows via webhook or polling. Store the returned salt_hex: you need it to re-verify the exact bytes later.
# Signing input (spec 7.3): RRv1\nPOST\n/v1/seal\nsha256(body)\nts\nnonce
DIGEST=$(shasum -a 256 policy.pdf | cut -d' ' -f1)
BODY='{"record_type":"policy","external_ref":"POL-2026-88123","content_digest_hex":"'"$DIGEST"'"}'
TS=$(($(date +%s) * 1000))                    # unix MILLISECONDS
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]') # single-use uuid
BODY_HASH=$(printf '%s' "$BODY" | shasum -a 256 | cut -d' ' -f1)
printf 'RRv1\nPOST\n/v1/seal\n%s\n%s\n%s' "$BODY_HASH" "$TS" "$NONCE" > /tmp/rr-input

# Raw Ed25519 signature over the canonical input, base64url, no padding:
SIG=$(openssl pkeyutl -sign -inkey request-key.pem -rawin -in /tmp/rr-input \
  | basenc --base64url -w0 | tr -d '=')

curl -sS -X POST https://api.roxregister.com/v1/seal \
  -H "Authorization: Bearer $RR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-RR-Key-Id: ish-prod-2026-01" \
  -H "X-RR-Timestamp: $TS" \
  -H "X-RR-Nonce: $NONCE" \
  -H "X-RR-Signature: $SIG" \
  -d "$BODY"
# → 202 {"record_id":"rec_…","reference":"rr_ref_01J8ZKQ2…","salt_hex":"…",
#        "status":"pending"}
# Accepted. Exactly one terminal outcome follows asynchronously:
# record.anchored (webhook) or record.failed. STORE salt_hex.
2 · Verify a record
GET /verify/{reference} · POST /verify/hash
Verification is public by design, no key, no login. A reference lookup returns status and anchor coordinates; a digest + salt check proves the exact bytes you hold are the bytes that were sealed. One changed byte flips the result to mismatch, which the vault owner sees as a tamper signal.
# Status check by reference, public, no API key, no salt needed:
curl -sS https://api.roxregister.com/verify/rr_ref_01J8ZKQ2M3TT9AHW5RC2E9KQ2A
# → {"result":"genuine","record_type":"policy",
#    "issuer":{"corporate_name":"…","vault_name":"…"},
#    "anchored":{"tx":"3B4YxyQGKr…","slot":18230441,"block_time":"…"},
#    "status":"anchored","lifecycle":[…],"verify_fields":{…}}

# Byte-exact check of the file you hold (needs the record's salt_hex,
# which /v1/seal returned when you sealed it):
DIGEST=$(shasum -a 256 policy.pdf | cut -d' ' -f1)
curl -sS -X POST https://api.roxregister.com/verify/hash \
  -H "Content-Type: application/json" \
  -d '{"content_digest_hex":"'"$DIGEST"'",
       "salt_hex":"'"$RR_SALT_HEX"'",
       "reference":"rr_ref_01J8ZKQ2M3TT9AHW5RC2E9KQ2A"}'
# → same shape plus "match": true|false, one changed byte flips it
#   to false and result to "mismatch" (counted as a tamper signal)
3 · Register a webhook & verify its signature
POST /v1/webhooks
Registration is a JWS mutation like sealing. Every delivery is signed v1=HMAC-SHA256(secret, ts + "." + body) over the exact raw body bytes, verify it constant-time before trusting a payload, and treat verification.mismatch as your tamper alarm.
# Register (a JWS mutation, same RRv1 signing as /v1/seal, path /v1/webhooks):
BODY='{"url":"https://api.ish.example/rox/events","events":["record.anchored","verification.mismatch","gas.low"]}'
TS=$(($(date +%s) * 1000)); NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')
BODY_HASH=$(printf '%s' "$BODY" | shasum -a 256 | cut -d' ' -f1)
printf 'RRv1\nPOST\n/v1/webhooks\n%s\n%s\n%s' "$BODY_HASH" "$TS" "$NONCE" > /tmp/rr-input
SIG=$(openssl pkeyutl -sign -inkey request-key.pem -rawin -in /tmp/rr-input \
  | basenc --base64url -w0 | tr -d '=')

curl -sS -X POST https://api.roxregister.com/v1/webhooks \
  -H "Authorization: Bearer $RR_API_KEY" -H "Content-Type: application/json" \
  -H "X-RR-Key-Id: ish-prod-2026-01" -H "X-RR-Timestamp: $TS" \
  -H "X-RR-Nonce: $NONCE" -H "X-RR-Signature: $SIG" \
  -d "$BODY"
# → 201 {"id":"wh_…","secret":"whsec_…"}   ← secret shown EXACTLY ONCE

# Each delivery you receive is signed; recompute and compare:
#   X-RR-Webhook-Timestamp: unix SECONDS
#   X-RR-Webhook-Signature: v1=<hex>
printf '%s.%s' "$TS_HEADER" "$RAW_BODY" \
  | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -hex
# must equal the signature after "v1=", compare constant-time in real code