Data Contract
HMAC Authentication

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

  1. The platform constructs a signing string from the request
  2. Signs it with a shared secret using HMAC-SHA256
  3. Sends the signature in the X-Signature header
  4. Your app verifies the signature before processing

Signature Construction

The signing string is built by concatenating:

{timestamp}.{requestBody}

Where:

  • timestamp — Unix timestamp in milliseconds (from X-Timestamp header)
  • 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_abc123

Replay Attack Prevention

To prevent replay attacks, your app should:

  1. Check the timestamp — Reject requests where X-Timestamp is more than 5 minutes old
  2. Track request IDs — Optionally store X-Request-Id values 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-contract SDK handles signature verification automatically. You only need to implement manual verification if you're not using the SDK.


© 2026 Shell Technology. All rights reserved.