Skip to content
Looking for our India delivery & GCC team?See the India site →

Webhooks

Get told when credits run low or a subscription changes instead of polling. Deliveries are signed, retried, and never slow down your API calls.

Register an endpoint

An endpoint must be a public https URL. A key can have up to 5. The signing secret is returned once, so store it now.

curl
curl https://api.helixhumancapital.in/v1/webhooks \
  -H "Authorization: Bearer $HELIX_KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/hooks/helix", "events": ["credits.low", "credits.exhausted"]}'
Python (requests)
r = requests.post("https://api.helixhumancapital.in/v1/webhooks",
    headers={"Authorization": f"Bearer {KEY}"},
    json={"url": "https://example.com/hooks/helix", "events": ["credits.low", "credits.exhausted"]})
secret = r.json()["secret"]   # shown once
Node.js (fetch)
const r = await fetch("https://api.helixhumancapital.in/v1/webhooks", {
  method: "POST",
  headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ url: "https://example.com/hooks/helix", events: ["credits.low", "credits.exhausted"] }),
})
const { secret } = await r.json() // shown once

Omit events (or send ["*"]) to receive everything. Manage endpoints with GET /v1/webhooks and DELETE /v1/webhooks/{id}.

Events

TypeWhendata
credits.lowRemaining credits fall to 20% of the period allowance. Once per period.allowance, remaining, periodEnds
credits.exhaustedRemaining credits reach zero. Once per period.allowance, remaining, periodEnds
subscription.renewedA payment succeeded and a new period started.plan, periodEnds, orderId
subscription.expiringThe period is about to end.periodEnds
subscription.expiredThe period and grace window have ended.periodEnded
webhook.testYou called the test endpoint.a sample payload

Delivery format

POST to your URL
content-type: application/json
user-agent: HelixAPI-Webhooks/1
helix-event: credits.low
helix-delivery: 0f6e…            (unique per event; use it to de-duplicate)
helix-signature: t=1800000000,v1=5257a869…

{
  "id": "0f6e…",
  "type": "credits.low",
  "created": 1800000000,
  "data": { "allowance": 3000, "remaining": 600, "periodEnds": "2026-12-09T17:15:28.318Z" }
}

Reply with any 2xx within 8 seconds to acknowledge. Redirects are not followed.

Verify the signature

v1 is the hex HMAC-SHA256 of <t>.<raw request body> using your endpoint secret. Verify against the raw bytes of the body before parsing JSON, compare in constant time, and reject timestamps more than 5 minutes old to stop replays.

Node.js (Express)
import crypto from "node:crypto"
import express from "express"

const app = express()
app.post("/hooks/helix", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("helix-signature") ?? ""
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")))
  const age = Math.abs(Date.now() / 1000 - Number(parts.t))
  const expected = crypto.createHmac("sha256", process.env.HELIX_WEBHOOK_SECRET)
    .update(`${parts.t}.${req.body.toString("utf8")}`).digest("hex")
  const ok = age < 300 && parts.v1?.length === expected.length &&
             crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected))
  if (!ok) return res.status(400).end()
  const event = JSON.parse(req.body)
  // …handle event.type, de-duplicate on event.id…
  res.status(200).end()
})
Python (Flask)
import hmac, hashlib, os, time, json
from flask import Flask, request, abort

app = Flask(__name__)

@app.post("/hooks/helix")
def helix():
    raw = request.get_data()                       # raw bytes, before any JSON parsing
    parts = dict(p.split("=", 1) for p in request.headers.get("helix-signature", "").split(","))
    expected = hmac.new(os.environ["HELIX_WEBHOOK_SECRET"].encode(),
                        f"{parts.get('t')}.".encode() + raw, hashlib.sha256).hexdigest()
    fresh = abs(time.time() - float(parts.get("t", 0))) < 300
    if not (fresh and hmac.compare_digest(parts.get("v1", ""), expected)):
        abort(400)
    event = json.loads(raw)
    return "", 200

Retries and shut-off

  • A non-2xx reply, timeout or network error is retried after 30 seconds, 2 minutes, 10 minutes, 1 hour, 6 hours and 24 hours: seven attempts in total.
  • Deliveries can arrive more than once and out of order. De-duplicate on helix-delivery / id.
  • An endpoint that fails 30 times in a row is switched off. Re-register it once fixed.
  • Destinations on private, loopback or link-local addresses, and plain http, are refused.

Test and debug

POST /v1/webhooks/{id}/test sends a signed webhook.test immediately. GET /v1/webhooks/{id}/deliveries lists the last 25 deliveries with status, attempts and the last response code or error.

Webhooks | Helix API documentation — Helix Human Capital