Skip to content

Overview

The V2 ingest endpoint (POST /api/v2/ingest) uses HMAC-SHA256 signatures to authenticate requests. This provides a secure, stateless authentication mechanism optimized for high-throughput event ingestion.

Prerequisites

Before you can sign requests, you need your project's HMAC secret:

  1. Navigate to your project in the dashboard
  2. Go to Project Settings
  3. Copy the HMAC secret, or retrieve it via the API:
bash
curl -X GET https://contox.dev/api/projects/proj_abc123/hmac-secret \
  -H "Authorization: Bearer contox_sk_yourkey"

Signing process

Step 1: Prepare the request body

Serialize your request payload as a JSON string. The body must be valid JSON:

json
{
  "event": "session_save",
  "payload": {
    "summary": "Implemented user authentication",
    "changes": []
  }
}

Step 2: Get the current timestamp

Generate a Unix timestamp (seconds since epoch):

javascript
const timestamp = Math.floor(Date.now() / 1000);

Step 3: Build the signing string

Concatenate the timestamp and the raw request body with a period (.) separator:

{timestamp}.{body}

For example:

1705312200.{"event":"session_save","payload":{"summary":"Implemented user authentication","changes":[]}}

Step 4: Compute the HMAC-SHA256 signature

Use your project's HMAC secret to compute the signature:

javascript
const crypto = require('crypto');

const signingString = `${timestamp}.${body}`;
const signature = crypto
  .createHmac('sha256', hmacSecret)
  .update(signingString)
  .digest('hex');

Step 5: Send the request

Include the required headers with your request:

bash
curl -X POST https://contox.dev/api/v2/ingest \
  -H "Content-Type: application/json" \
  -H "X-Contox-Project: proj_abc123" \
  -H "X-Contox-Timestamp: 1705312200" \
  -H "X-Contox-Signature: sha256=a1b2c3d4e5f6..." \
  -d '{"event":"session_save","payload":{...}}'

Required headers

HeaderDescription
X-Contox-ProjectYour project ID
X-Contox-TimestampUnix timestamp used in signing
X-Contox-SignatureThe computed signature, prefixed with sha256=

Timestamp validation

The server rejects requests where the timestamp is more than 5 minutes old. This prevents replay attacks. Ensure your system clock is synchronized.

Complete examples

Node.js

javascript
const crypto = require('crypto');

async function ingestEvent(projectId, hmacSecret, event) {
  const body = JSON.stringify(event);
  const timestamp = Math.floor(Date.now() / 1000);
  const signingString = `${timestamp}.${body}`;

  const signature = crypto
    .createHmac('sha256', hmacSecret)
    .update(signingString)
    .digest('hex');

  const response = await fetch('https://contox.dev/api/v2/ingest', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Contox-Project': projectId,
      'X-Contox-Timestamp': String(timestamp),
      'X-Contox-Signature': `sha256=${signature}`,
    },
    body,
  });

  return response.json();
}

Python

python
import hmac
import hashlib
import json
import time
import requests

def ingest_event(project_id, hmac_secret, event):
    body = json.dumps(event, separators=(',', ':'))
    timestamp = str(int(time.time()))
    signing_string = f"{timestamp}.{body}"

    signature = hmac.new(
        hmac_secret.encode(),
        signing_string.encode(),
        hashlib.sha256
    ).hexdigest()

    response = requests.post(
        'https://contox.dev/api/v2/ingest',
        headers={
            'Content-Type': 'application/json',
            'X-Contox-Project': project_id,
            'X-Contox-Timestamp': timestamp,
            'X-Contox-Signature': f'sha256={signature}',
        },
        data=body,
    )

    return response.json()

Troubleshooting

ErrorCauseFix
Invalid signatureSignature mismatchVerify the signing string format and secret
Timestamp expiredRequest too oldSynchronize your system clock
Missing required headerHeader not includedInclude all three X-Contox-* headers

Next steps