HMAC Authentication
All Data Contract requests are authenticated using HMAC-SHA256 signatures. This ensures that only the platform can trigger data operations on your app.
How It Works
- The platform constructs a signing string from the request
- Signs it with a shared secret using HMAC-SHA256
- Sends the signature in the
X-Signatureheader - Your app verifies the signature before processing
Signature Construction
The signing string is built by concatenating:
{timestamp}.{requestBody}Where:
timestamp— Unix timestamp in milliseconds (fromX-Timestampheader)requestBody— The raw JSON request body
The signature is the hex-encoded HMAC-SHA256 of this string.
Request Headers
POST /data-contract/describe HTTP/1.1
Content-Type: application/json
X-Signature: a1b2c3d4e5f6...
X-Timestamp: 1709312400000
X-Request-Id: req_abc123Replay Attack Prevention
To prevent replay attacks, your app should:
- Check the timestamp — Reject requests where
X-Timestampis more than 5 minutes old - Track request IDs — Optionally store
X-Request-Idvalues to reject duplicates
const MAX_AGE_MS = 5 * 60 * 1000 // 5 minutes
function isTimestampValid(timestamp: number): boolean {
return Math.abs(Date.now() - timestamp) < MAX_AGE_MS
}Verification Example
import { createHmac, timingSafeEqual } from 'node:crypto'
function verifySignature(
secret: string,
body: string,
signature: string,
timestamp: string
): boolean {
// Reject stale requests
const ts = parseInt(timestamp, 10)
if (Math.abs(Date.now() - ts) > 5 * 60 * 1000) {
return false
}
// Compute expected signature
const signingString = `${timestamp}.${body}`
const expected = createHmac('sha256', secret)
.update(signingString)
.digest('hex')
// Constant-time comparison
const a = Buffer.from(signature)
const b = Buffer.from(expected)
if (a.length !== b.length) return false
return timingSafeEqual(a, b)
}Usage in Express Middleware
app.use('/data-contract', (req, res, next) => {
const signature = req.headers['x-signature'] as string
const timestamp = req.headers['x-timestamp'] as string
const valid = verifySignature(
process.env.DATA_CONTRACT_SECRET!,
JSON.stringify(req.body),
signature,
timestamp
)
if (!valid) {
return res.status(401).json({
status: 'error',
error: { code: 'INVALID_SIGNATURE', message: 'Invalid or expired signature' },
})
}
next()
})Note: The
@shellapps/data-contractSDK handles signature verification automatically. You only need to implement manual verification if you're not using the SDK.