Integrate Zoza Sign
Catch UI-vs-bytes lies before they cost you $1.46B. Drop the verifier into your signing flow in 30 minutes — REST API, signed audit receipts, free decoder for end-users.
Sign is positioned narrowly: independent decode + intent comparison + audit receipt. It is not a hardware-wallet replacement, a transaction simulator, or a risk scorer. See the competitor matrix on /about/sign for honest scope.
If you're a regulated custody team, also read the "What's NOT yet built" section before scoping a production integration.
Quick Start
The fastest path: hit the free decoder endpoint with raw transaction bytes, get back the decoded fields, eyeball the diff against what your UI claims.
curl -X POST https://sign-api.zoza.world/v1/decode \
-H "Content-Type: application/json" \
-d '{"raw_tx":"0000000000000001000000000000002a0000000000005208742d35cc6634c0532925a3b844bc9e7595f2bd2800000000000000000000000000000000000000000000000de0b6b3a7640000"}'
# Response:
# {
# "type": "transfer",
# "chain_id": 1,
# "nonce": 42,
# "to": "0x742d35cc6634c0532925a3b844bc9e7595f2bd28",
# "value": "1000000000000000000",
# "value_human": "1.000000 ETH"
# }
Authenticated POST /v1/verify compares the decoded output against your claimed intent and returns a signed audit receipt. Used by exchanges in the signing pipeline before any approval reaches a hardware wallet.
Pick Your Integration
Two integration tiers. Most production teams use both.
Free Decoder
Public POST /v1/decode. No auth. Rate-limited per IP. Use it to power "show me what I'm actually signing" UX in your wallet, custody dashboard, or block explorer.
Verify API + Audit Receipts
Authenticated POST /v1/verify. Compares claimed intent vs decoded reality, flags mismatches, returns Ed25519-signed audit receipt for compliance. Counts toward your daily quota.
Apply for an API Key
The free /v1/decode endpoint needs no key — try it from any browser, any backend, any time. You only need an API key for /v1/verify (signed receipts + per-call quota tracking).
Fill the form below — we review within 24 hours and email your zsk_live_... key on approval. Keys are shown once at issuance.
Authentication
Pass your key as a Bearer token on every /v1/verify and /v1/verifications/* call:
Authorization: Bearer zsk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
POST /v1/decode needs no header at all — open to everyone.
Base URL
https://sign-api.zoza.world
All API paths below are relative to that base.
POST /v1/decode
{
"raw_tx": "0x0000000000000001..."
}
{
"type": "transfer",
"chain_id": 1,
"nonce": 42,
"gas_limit": 21000,
"to": "0x742d35cc6634c0532925a3b844bc9e7595f2bd28",
"value": "1000000000000000000",
"value_human": "1.000000 ETH",
"token": "ETH",
"data": "",
"method": ""
}
Recognized method signatures: transfer(address,uint256), approve(address,uint256) with infinite-allowance detection. Other 4-byte selectors return method: "unknown(0xa9059cbb)" + raw data for inspection.
POST /v1/verify
{
"intent": {
"type": "transfer",
"to": "0x742d35cc6634c0532925a3b844bc9e7595f2bd28",
"amount": "1.000000",
"token": "ETH",
"chain": "ethereum",
"description": "Withdrawal to user wallet"
},
"raw_tx": "0x0000000000000001..."
}
{
"id": "ver_a1b2c3...",
"status": "match",
"risk_level": "safe",
"mismatches": [],
"decoded_tx": { /* same shape as /v1/decode */ },
"claimed_intent": { /* echoed back */ },
"receipt": {
"verification_id": "ver_a1b2c3...",
"exchange_id": "exch_xyz...",
"status": "match",
"timestamp": "2026-04-17T13:42:00Z",
"signature": "base64-ed25519-sig",
"signer_pub": "base64-ed25519-pubkey"
},
"verified_at": "2026-04-17T13:42:00Z"
}
{
"id": "ver_x9y8z7...",
"status": "mismatch",
"risk_level": "critical",
"mismatches": [
{
"field": "to",
"claimed": "0x742d35Cc...f2bD28",
"actual": "0xDEADBEEF...DEADBEEF",
"severity": "critical"
}
],
/* decoded_tx + claimed_intent + receipt as above */
}
Sign returns 200 OK with status:"mismatch" — not an HTTP error. Your signing pipeline must inspect status and risk_level and block on "critical". Logging-only integrations defeat the purpose.
GET /v1/verifications/{id}
GET /v1/authority
{
"algorithm": "ed25519",
"public_key": "base64-pubkey",
"context": "sign_receipt",
"note": "Use this key with verifyReceipt() to verify any receipt offline."
}
Receipt Shape
Every successful /v1/verify embeds a receipt object:
| Field | Type | Description |
|---|---|---|
| verification_id | string | Sign's internal ID — pin to your audit row |
| exchange_id | string | Your account ID — pin to your customer record |
| status | string | "match" | "mismatch" | "suspicious" | "error" |
| timestamp | RFC3339 | UTC time the comparison ran |
| signature | bytes (base64) | Ed25519 over JSON of the four fields above, with context "sign_receipt" |
| signer_pub | bytes (base64) | The pubkey that produced the signature — pinned for offline verify |
Verify a Receipt Offline
You don't need to trust Sign's API to validate any receipt. Pull the canonical pubkey, recompute the JSON, and verify the signature locally.
import * as ed from '@noble/ed25519';
async function verifyReceipt(receipt) {
const canonical = JSON.stringify({
id: receipt.verification_id,
exchange_id: receipt.exchange_id,
status: receipt.status,
timestamp: receipt.timestamp
});
const message = new TextEncoder().encode('sign_receipt|' + canonical);
const sig = Buffer.from(receipt.signature, 'base64');
const pub = Buffer.from(receipt.signer_pub, 'base64');
return await ed.verify(sig, message, pub);
}
Storage & Audit
Treat receipts the way you treat database backups — keep them for as long as your regulator says (typically 7 years for FATF Travel Rule). They're small (~512 bytes JSON), append-only, and provably tamper-evident. Recommended layout:
// One row per signed verification
CREATE TABLE sign_receipts (
verification_id TEXT PRIMARY KEY,
exchange_id TEXT NOT NULL,
status TEXT NOT NULL,
raw_tx TEXT NOT NULL, -- full hex you sent in
intent_json JSONB NOT NULL, -- full intent you sent in
decoded_json JSONB NOT NULL, -- decoded_tx returned by Sign
receipt_json JSONB NOT NULL, -- full receipt object
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Mismatch Classes
Sign emits four mismatch classes today. Each has documented severity — your pipeline should map severity to your incident response.
| Field | Severity | What it means | Real-world example |
|---|---|---|---|
| to | critical | Decoded destination differs from claimed destination (case-insensitive comparison). | Bybit Feb 2025 — UI showed Safe address, bytes pointed to attacker proxy. |
| amount | critical | Decoded value differs from claimed amount (numeric comparison, suffix-stripped). | Skim attacks — UI claims 50 ETH withdrawal, bytes drain 500 ETH. |
| type | warning | Decoded transaction type differs from claimed type (transfer vs approval, etc.). | UI claims "transfer", calldata is setApprovalForAll(spender, true). |
| approval | critical | Decoded approval is unlimited (max uint256). Always flagged regardless of intent. | Permit2 ice-phishing — spender granted infinite allowance. |
Error Codes
| Code | Meaning | Action |
|---|---|---|
| 400 | Invalid request (missing field, malformed hex, intent JSON unparseable) | Read error field, fix request, retry |
| 401 | Missing / invalid / revoked API key | Check Authorization header, rotate key if compromised |
| 404 | Verification ID not found | ID may have expired (90-day window) or was never issued |
| 409 | Application already approved/rejected | Don't double-submit application |
| 429 | Rate limit exceeded (per IP for /decode, per email/IP for /applications) | Back off and retry after 24h or upgrade plan |
| 500 | Internal Sign error | Retry with exponential backoff. If persistent, email hello@zoza.world |
Rate Limits & Plans
Free
- Free decoder unlimited (IP-rate-limited)
- 1K signed verifications/day
- Email support, best-effort
Pro
- Free decoder unlimited
- 100K signed verifications/day
- Receipts retained 12 months
- Email support, 24h SLA
Enterprise
- Dedicated authority key
- Receipts retained 7+ years (FATF)
- SOC 2 report (when available)
- Source-access NDA + audit hooks
- Slack/PagerDuty incident channel
Quota resets at UTC midnight. Every authenticated response includes X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset-UTC headers.
CORS
The decoder endpoint allows requests from any origin (used by browser-based wallets and the live decoder on /about/sign). The verify endpoint also allows any origin but requires a valid API key — keys should never live in browser code in production. Run verify from your backend.
Going Live Checklist
Walk this list before pointing Sign at production withdrawals.
- Pin the authority pubkey. Fetch
/v1/authorityat deploy time, embed in your config. Verify it matches thesigner_pubin receipts. - Block on critical, alert on warning. Don't log-only — the whole point is preventing the signature, not documenting it.
- Store full receipts. Schema above. Don't extract status only — you need the signature for audit.
- Test the Bybit scenario. Build a fixture where intent says address A but raw_tx points to address B. Confirm your pipeline blocks.
- Test the unlimited-approval scenario. Build a fixture with
approve(spender, MAX_UINT256). Confirm critical mismatch fires. - Set up alerting. Webhook delivery isn't shipped yet (see gap analysis) — for now, poll
/v1/verifications/{id}after each signature attempt and page on mismatch. - Plan for authority rotation. Quarterly. When we rotate, the new pubkey will be served at
/v1/authorityand the old pubkey continues to validate historical receipts. - Sign the source-access NDA if you need to audit the decoder yourself. Email hello@zoza.world.
Support
- Email: hello@zoza.world — response within 24h on Pro+, best-effort on Free.
- X / Twitter: @zoza_world for product updates and incident notices.
- Bug bounty: see shield-bounty.html — Sign mirrors the same scope and payouts.
- Status page (planned):
status.zoza.world— outage history and rolling SLO measurements.
The overview page documents what Sign does NOT do — competitor matrix, attack surface analysis, and the honest "what's not yet built" gap list. If you're a custody team scoping a regulated integration, that page is required reading.