Skip to main content

When to use this

If the system calling your Codika endpoint signs outbound webhooks automatically (like Resend, Stripe, GitHub, Clerk, or any Svix-based platform), use webhook signature verification instead of API keys.
API Key (X-API-Key)Webhook Signature (HMAC)
Best forYour own dashboards, curl, Postman, ZapierExternal platforms that sign webhooks
SetupCopy key, paste in headerRegister URL in platform, paste signing secret in Codika
Secret on the wireYes (key sent in every request)No (only a signature derived from the secret)
Replay protectionNoYes (5-minute timestamp window)
Body tamper detectionNoYes (body is part of the signed payload)

How it works

Codika follows the Standard Webhooks specification (used by Resend, Clerk, Dub, and others). When the external platform sends a request to your Codika endpoint, it includes three headers:
HeaderPurposeExample
webhook-idUnique message identifiermsg_2xK9a8B...
webhook-timestampUnix timestamp (seconds)1711987200
webhook-signatureHMAC-SHA256 signaturev1,K7gNU3sdo+OL0...
The signature is computed as:
HMAC-SHA256(
  key: base64_decode(signing_secret),
  message: "{webhook-id}.{webhook-timestamp}.{request_body}"
)
Codika verifies the signature and rejects the request if:
  • The signature doesn’t match (tampered body or wrong secret)
  • The timestamp is older than 5 minutes (replay attack)
  • No signing secret is configured for that endpoint

Setup

Step 1: Get your webhook URL

Open the API Access sheet from your process instance card (click the key icon). Each HTTP endpoint shows its public URL:
https://api.codika.io/webhook/{processInstanceId}/{workflowId}
Copy this URL.

Step 2: Register the URL in your external platform

Paste the Codika webhook URL in your platform’s webhook configuration. When you save, the platform generates a signing secret (typically prefixed whsec_).

Step 3: Paste the signing secret into Codika

Back in Codika’s API Access sheet, find the endpoint you registered and paste the signing secret in the “Signing Secrets” section. Optionally add a label (e.g., “Resend production”). That’s it. The external platform will now sign every request, and Codika will verify it automatically.

Sending signed requests manually

If you’re building your own webhook delivery system, here’s how to sign requests:

TypeScript / Node.js

import crypto from 'crypto';

async function sendSignedWebhook(
  url: string,
  signingSecret: string,
  payload: object
) {
  const body = JSON.stringify(payload);
  const msgId = `msg_${crypto.randomUUID().split('-')[0]}`;
  const timestamp = Math.floor(Date.now() / 1000).toString();

  // Strip whsec_ prefix and base64-decode the secret
  const secretBase64 = signingSecret.replace('whsec_', '');
  const secretKey = Buffer.from(secretBase64, 'base64');

  // Sign: HMAC-SHA256("{msg_id}.{timestamp}.{body}")
  const signature = crypto
    .createHmac('sha256', secretKey)
    .update(`${msgId}.${timestamp}.${body}`)
    .digest('base64');

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'webhook-id': msgId,
      'webhook-timestamp': timestamp,
      'webhook-signature': `v1,${signature}`
    },
    body
  });

  return response.json();
}

cURL

TIMESTAMP=$(date +%s)
BODY='{"event":"invoice.paid","data":{"id":"inv_42"}}'
MSG_ID="msg_test123"
SECRET_B64="your_base64_secret_here"  # The part after whsec_

SIGNATURE=$(echo -n "${MSG_ID}.${TIMESTAMP}.${BODY}" \
  | openssl dgst -sha256 -hmac "$(echo ${SECRET_B64} | base64 -d)" -binary \
  | base64)

curl -X POST \
  -H "Content-Type: application/json" \
  -H "webhook-id: ${MSG_ID}" \
  -H "webhook-timestamp: ${TIMESTAMP}" \
  -H "webhook-signature: v1,${SIGNATURE}" \
  https://api.codika.io/webhook/{processInstanceId}/{workflowId} \
  -d "${BODY}"

Payload format

When using webhook signatures, the request body is passed through as-is to the workflow. There’s no need for the {"payload": {...}} wrapper used with API key authentication — Codika handles both formats.

Multiple secrets per endpoint

Each endpoint can have multiple signing secrets. This is useful for:
  • Secret rotation: Add the new secret, update the external platform, then remove the old one. Both secrets are valid during the transition.
  • Multiple sources: Different systems can send to the same endpoint, each with their own signing secret.
Codika checks all stored secrets for a given endpoint and accepts the request if any one matches.

Mixing auth methods

All authentication methods coexist on the same endpoint. Codika checks in order:
  1. Webhook signature headers (HMAC) — if webhook-signature, webhook-id, and webhook-timestamp headers are present
  2. API key in headersX-API-Key or Authorization: Bearer
  3. API key in query parameter?api_key= in the URL
  4. Organization keyX-Process-Manager-Key header
The first match wins. You can use different methods for different callers on the same endpoint.

Security advantages

  • The secret never travels on the wire. Only a signature (derived from the secret) is sent. Even if someone intercepts the request, they can’t extract the secret.
  • Replay protection. Requests older than 5 minutes are rejected, preventing captured requests from being replayed later.
  • Tamper detection. The request body is part of the signed message. Any modification to the body invalidates the signature.