Skip to main content
Use these vectors to verify your signer matches EchoZero and the official SDK.
Inbound vs outbound: Inbound agent signals sign canonical JSON (sorted keys, known field set). Outbound signal.execution webhooks sign the raw request body string. REST HMAC signs timestamp + METHOD + path + body.

REST HMAC (POST)

FieldValue
Secrettest_secret
Timestamp (ms)1710000000000
MethodPOST
Path/api/api-keys
Body{"name":"SDK HMAC Test"}
String to sign1710000000000POST/api/api-keys{"name":"SDK HMAC Test"}
Expected x-signature274c9ff280eadf751530e9e7fce2c2a573d8676b13b7108fe353407df7cc9e00

Inbound webhook (text + idempotencyKey)

FieldValue
Secrettest_secret
Timestamp (s)1710000000
Body{"idempotencyKey":"sdk-test-1","text":"BUY SOL 500 USDC"}
Canonical JSON{"idempotencyKey":"sdk-test-1","text":"BUY SOL 500 USDC"}
Payload1710000000.{"idempotencyKey":"sdk-test-1","text":"BUY SOL 500 USDC"}
Expected X-EZ-Signature1d30d896fc609e62bcf9be991c1dc1177a9c63219909869ef2846bad4685de3b

Inbound webhook (structured buy, unknown fields stripped)

FieldValue
Secretsecret
Timestamp (s)1710000000
Body fieldseventType, idempotencyKey, reasoning, tokenAddress, amount
Extra fieldsIgnored for signing (e.g. unknownField)
Expected X-EZ-Signaturebe420f61d91e6b871481774c62f972f0aafa6f5f1a727ba1e4a32558784f77c3
Canonical JSON for that vector:
{"amount":500,"eventType":"buy","idempotencyKey":"test-1","reasoning":"test","tokenAddress":"So11111111111111111111111111111111111111112"}

Inbound canonicalization rules

  1. Only signing-eligible keys are included (text, idempotencyKey, and structured fields from the #1098 schema).
  2. Object keys are sorted alphabetically at every nesting level.
  3. Unknown top-level fields are omitted from the canonical form.
  4. Numbers must be finite JSON numbers (no NaN / Infinity).
Match the SDK implementation: inbound_canonical.rs (reference for all languages).

Outbound signal.execution

FieldValue
Secretwebhook_secret
Timestamp2026-07-06T12:00:00.000Z
Raw body{"event":"signal.execution","signalId":"sig_1","developerAgentId":"agent_1","status":"executed","timestamp":"2026-07-06T12:00:00.000Z"}
Payload{timestamp}.{raw_body}
AlgorithmHMAC-SHA256 → lowercase hex
Expected x-echozero-signature249d84b9e89206f6aee4b124f728c945b805666b3102d76b913a17f1bf568574
Verify with the same raw bytes you receive on the wire (do not re-serialize JSON).

Inbound webhook (nested buy with triggers + context)

FieldValue
Secrettest_secret
Timestamp (s)1710000000
Canonical JSONsee below
Expected X-EZ-Signature3afa3bf158361edcb46dbeaee6b128f7cd1c78baa28824f2355de1223671e67a
{"amount":500,"chain":"solana","context":{"indicators":["VWAP","RSI"],"marketCondition":"trending","price":148.5},"eventType":"buy","idempotencyKey":"nested-vector-001","instrument":{"tokenAddress":"So11111111111111111111111111111111111111112"},"reasoning":"Nested canonicalization test.","symbol":"SOL","triggers":{"slPrice":{"targetType":"price","value":165},"tps":[{"label":"TP1","sizePct":50,"targetType":"price","value":180}]}}
Nested objects must be sorted at every level (slPrice before tps; targetType before value inside each TP). This is where cross-language signing bugs usually appear.

curl self-test (REST)

SECRET="test_secret"
TS="1710000000000"
REQ_PATH="/api/api-keys"
BODY='{"name":"SDK HMAC Test"}'
SIG=$(printf '%s' "${TS}POST${REQ_PATH}${BODY}" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
echo "$SIG"
# expect 274c9ff280eadf751530e9e7fce2c2a573d8676b13b7108fe353407df7cc9e00