Webhooks

Hubrix processes inbound webhooks from Stripe for billing events. Outbound webhooks (Hubrix → your server) are in preview and not yet active in production.


Inbound webhooks (Stripe)

Stripe sends billing events to Hubrix at POST https://api.hubrix.ai/v1/billing/webhook. This endpoint is public (no Bearer token) but requires a valid Stripe HMAC signature.

Handled events

Stripe eventHubrix action
checkout.session.completedCreate/update subscription, allocate monthly credits, send subscription_started email
invoice.payment_succeededRenew subscription period, re-allocate credits, send subscription_renewed email
invoice.payment_failedSet subscription to past_due, send payment_failed email
customer.subscription.updatedUpdate plan, seats, cancel_at_period_end flag
customer.subscription.deletedSet subscription to cancelled, send subscription_cancelled email
customer.subscription.trial_will_endSend trial_ending_in_2d email (Stripe fires 3 days before)
payment_intent.succeededRecord payment in stripe_payments for top-up idempotency

Stripe webhook payload shapes are documented in the Stripe API reference. We do not duplicate them here.


Outbound webhooks (preview)

Preview. Outbound webhooks are not yet active. This documents the planned behaviour; subscribe at api@hubrix.ai for early access.

When outbound webhooks are live, configure endpoints from Settings → Webhooks in the Hubrix UI. You can subscribe per-event or to all events.

Planned events

Event typeTriggerKey payload fields
workflow.run.completedWorkflow run reaches completed or failed statusrun_id, workflow_id, status, outputs, error
research.report.readyResearch report reaches completed statusreport_id, query, depth, content_length
bulk.job.completedBulk job finishes (all rows processed)job_id, total_rows, rows_completed, rows_failed
connector.sync.failedConnector sync job fails after retriesconnector_id, folder_id, error, attempt

Payload structure

{
  "id":         "evt_01HZ3K9ABCDEF",
  "type":       "workflow.run.completed",
  "created_at": "2026-05-04T17:00:00Z",
  "company_id": "uuid-of-company",
  "data": {
    "run_id":      "uuid-of-run",
    "workflow_id": "uuid-of-workflow",
    "status":      "completed",
    "outputs":     { "result": "..." },
    "error":       null
  }
}

Signature verification

Every outbound request includes X-Hubrix-Signature: t=<unix_ts>,v1=<hex_hmac_sha256>. Compute the expected signature as:

HMAC-SHA256(secret, "{timestamp}.{request_body}")

Verification examples:

# Python
import hmac, hashlib

def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in signature.split(","))
    ts, sig = parts.get("t", ""), parts.get("v1", "")
    expected = hmac.new(
        secret.encode(),
        f"{ts}.{body.decode()}".encode(),
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, sig)
// JavaScript
import crypto from 'crypto';

function verifyWebhook(body: string, signature: string, secret: string): boolean {
  const parts = Object.fromEntries(signature.split(',').map(p => p.split('=')));
  const { t: ts, v1: sig } = parts;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${ts}.${body}`)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}

Retry policy

If your endpoint returns a non-2xx status, Hubrix retries with this schedule:

AttemptDelay
1 (initial)Immediate
2+30 seconds
3+5 minutes
4+1 hour
5+6 hours
DLQAfter 5 failures, event moves to dead-letter queue

Each delivery includes an Idempotency-Key header (UUID) so you can safely de-duplicate retried events.


Receiving webhook events

Your endpoint must: