Elixpo Mails

Elixpo Mails Docs

Authentication

Every trigger request is authenticated by an HMAC-SHA256 signature computed with your product's shared secret. There is no bearer token — the signature both authenticates you and proves the body wasn't tampered with.

The signature header

Send your signature in the X-Elixpo-Signature header. It carries a timestamp and a v1 HMAC, comma-separated:

http
X-Elixpo-Signature: t=1718500000,v1=9f8a...e21c
  • t — the unix time in seconds when you signed the request.
  • v1 — the hex-encoded HMAC-SHA256.

What you sign

The signed payload string is `${t}.${rawBody}` — the timestamp, a literal dot, then the exact JSON body bytes you send. The HMAC key is the product's shared secret.

javascript
import crypto from "node:crypto";

const secret = "YOUR_PRODUCT_SECRET";

// 1. Serialize the body ONCE. These are the exact bytes you will send.
const rawBody = JSON.stringify({ to: "user@example.com", variables: { name: "Ada" } });

// 2. Current unix time in seconds.
const t = Math.floor(Date.now() / 1000);

// 3. HMAC-SHA256 over `${t}.${rawBody}` with the product secret.
const v1 = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");

// 4. Send the header alongside the SAME rawBody.
const signature = `t=${t},v1=${v1}`;

Sign the bytes you send

Serialize the body once and reuse that exact string for both the signature and the request body. If you re-serialize (e.g. let a framework stringify it again), the bytes can differ and the signature will fail with 401.

Timestamp tolerance

Requests whose t is outside a 5-minute tolerance of server time are rejected with 400. Sign each request fresh at send time; don't cache signatures.

Secret rotation

When you roll a product's secret, the previous secret is still accepted for a short grace window. This lets you deploy the new secret without dropping in-flight requests. After the window closes, only the new secret works.

Why a request is rejected

  • 401 — bad or missing signature.
  • 400 — invalid timestamp (outside tolerance) or invalid recipient.
  • 403 — the webhook/product is disabled, or no signing secret is configured.
Next: Triggering

QuickstartTriggering