COMET

Step 2 — Set up encryption

Every request payload is encrypted using JWE (JSON Web Encryption) with AES-256-GCM. Your CLIENT_SECRET never travels in requests — it is used locally to derive the encryption key.

Only the server, which holds the same salt, can decrypt the payload.

Install

npm install jose

Encryption helper

// src/lib/apiClient.js
import { EncryptJWT } from 'jose'
import { createHmac } from 'crypto'

const CLIENT_ID     = process.env.CLIENT_ID
const CLIENT_SECRET = process.env.CLIENT_SECRET
const CLIENT_SRVC   = process.env.CLIENT_SRVC
const BASE_URL      = process.env.API_BASE_URL   // https://your-api.com
const SALT          = process.env.KEY_DERIVATION_SALT

function deriveKey(secret) {
  return createHmac('sha256', SALT)
    .update(secret)
    .digest()              // returns 32-byte Buffer
}

export async function apiCall(endpoint, data) {
  const key   = deriveKey(CLIENT_SECRET)
  const token = await new EncryptJWT({ data, srvc: CLIENT_SRVC })
    .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })
    .setIssuedAt()
    .setExpirationTime('5m')
    .setJti(crypto.randomUUID())
    .encrypt(key)

  const res = await fetch(`${BASE_URL}/api/v01/${endpoint}`, {
    method:  'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-client-id':  CLIENT_ID,
    },
    body: JSON.stringify({
      data: token,
      srvc: CLIENT_SRVC,
    }),
  })

  const body = await res.json()

  if (!body.ok) {
    const err  = new Error(body.memo ?? 'API error')
    err.status = res.status
    err.txId   = res.headers.get('X-Transaction-Id')
    throw err
  }

  return body.data
}

Token expiry

Tokens expire after 5 minutes. Generate a new token for each request — do not reuse tokens. Each token includes a unique JTI (jti) to prevent replay attacks.