Skip to main content
Every request Castro sends to your server carries three headers: For requests without a body (GET, DELETE), the signature is computed over "{timestamp}.": the timestamp, a dot, and an empty string.

Minimum: check the API key

At the very least, reject any request whose X-API-Key doesn’t equal your stored key. This alone prevents anyone else from writing to your endpoints. The signature proves the request was produced by someone holding the key and that the body wasn’t modified in transit. Compute the HMAC over the raw request bytes (before JSON parsing) and compare with a constant-time check:
Sign the raw body bytes, not a re-serialized version of the parsed JSON: key order or whitespace differences would break the comparison. In Express, capture the raw body with express.json({ verify: (req, res, buf) => (req.rawBody = buf) }).

Replay protection

Reject requests whose X-Castro-Timestamp is more than a few minutes old. Note the unit: Castro signs with Date.now(), so it’s milliseconds. A seconds-based comparison makes every request look ~55,000 years in the future, and the check silently passes everything.

The handshake challenge

When the user connects their site, Castro POSTs { "challenge": "<hex>" } to your /handshake. Respond with challenge_response = HMAC-SHA256(challenge, api_key) (hex). This proves your server holds the same key, completing the trust loop in both directions. Note the formula: the challenge string on its own. No timestamp, no dot, no body. That shape is only for request signatures.

The three mistakes everyone makes

The HMAC covers the exact bytes on the wire. JSON.parse() followed by JSON.stringify() can reorder keys and drop whitespace, so the signature matches for some payloads and not others, which reads as an intermittent, unreproducible bug.Capture the raw body and hash that.
time() in PHP and time.time() in Python are seconds. Castro sends milliseconds. Multiply before you compare. See above.
The conformance script checks all three, plus that a bad signature is actually rejected. Run it before you connect.

Key rotation

Regenerating the key in Castro immediately invalidates the old one and marks the site disconnected. Update the key on your server, then click Connect website again.