Webhooks

Outgoing webhook

Era posting its events to your address, signed: body, headers, signature checks and retries.

POSThttps://app.getera.iohttps://your-server.example/era-webhook
HMAC-SHA256 signature

With an outgoing webhook the direction is reversed: when the events you chose happen, Era sends a POST request to your address. For example, it tells your accounting system or your own server whenever a new lead comes in.

Creating an endpoint

New endpoint

On Settings → Advanced → Webhooks (/settings/webhooks) click New endpoint and choose We call them as the Direction.

Address and events

Type a Name and the URL. In Events, list the event names you want, separated by commas, e.g. crm.lead_captured. Use crm.* for every event of a module. Leave it empty and every event is sent.

Turn on signing

In the endpoint's detail, under Signing, click Generate secret. The secret (it starts with whsec_) is shown once; store it on the receiving side. Endpoints without one are marked Unsigned in the list.

Try it

Send test under Try it sends a signed test event through the same queue, timeout and history as a real delivery.

The request Era sends

Body:

{
  "id": "1842",
  "event": "crm.lead_captured",
  "recordId": "8b1d0c4e-0000-4000-8000-000000000010",
  "occurredAt": "2026-10-14T09:30:00.000Z",
  "workspaceId": "WORKSPACE_ID",
  "data": { "id": "8b1d0c4e-0000-4000-8000-000000000010", "customerId": null, "affiliateId": null }
}

data is the event's own payload; it differs per event.

Headers:

HeaderValue
content-typeapplication/json
user-agentEra-Webhook/1
idempotency-key<endpoint id>:<event id> — stays the same across retries; use it to avoid processing twice.
x-era-eventThe event name, e.g. crm.lead_captured.
x-era-attemptWhich attempt this is (starting at 1).
x-era-timestampWhen it was signed (Unix seconds).
x-era-signaturet=<time>,v1=<signature>. While a secret is being rotated, there are two v1 values.
x-era-signedOnly on unsigned endpoints: none.

Verifying the signature

Each v1 is an HMAC-SHA256 computed with the secret over <time>.<raw body>. Use the body exactly as received — do not parse and re-serialise it. Reject timestamps older than five minutes. The request is valid if any value matches.

import crypto from 'node:crypto'

export function verifyEraSignature(rawBody, header, secret) {
  const parts = header.split(',')
  const t = Number(parts.find((p) => p.startsWith('t='))?.slice(2))
  const signatures = parts.filter((p) => p.startsWith('v1=')).map((p) => p.slice(3))
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  return signatures.some(
    (s) => s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected))
  )
}

When you click Rotate secret, the old secret keeps signing for another 24 hours; move the receiver to the new one in that time.

Your response and retries

  • A 2xx status counts as delivered. Any other status, a timeout or a connection error is a failed attempt.
  • Failed deliveries are retried; the wait grows a little each time (from a few seconds up to ten minutes).
  • Endpoint settings: Timeout (ms) defaults to 15000; Attempts before giving up defaults to 5 (1–10).
  • After many consecutive failures the endpoint switches itself off (a workspace threshold, 100 by default). One successful delivery resets the count.
  • Redirects are not followed, and only public addresses are called.

The delivery history shows each attempt's status and duration; Replay this delivery sends the same body again.

See also

© 2026 Era. Era documentation. Something missing or wrong? Write to us: [email protected]