Skip to main content

Billing endpoints

/billing/* and /platform/organizations/{id}/billing/* groups — plan lookup, usage/quota overview, checkout session generation and customer-portal access.

The implementation behind may be Stripe, the mock gateway (dev) or disabled — depends on NATIVE_WAZUH_BILLING_GATEWAY.

Endpoint table

MethodPathAuthDescription
GET/billing/plans/publicPublicLists plans visible to the public.
POST/billing/webhooks/stripeStripe signatureInternal — not called by clients.
GET/platform/organizations/{organization_id}/billingSessionSubscription + usage overview.
POST/platform/organizations/{organization_id}/billing/checkoutSessionCreates a checkout session (upgrade).
POST/platform/organizations/{organization_id}/billing/portalSessionCreates a URL for the customer portal.

GET /billing/plans/public

Lists available plans. Public route — useful for a pricing page that doesn't require login.

Response 200: array of BillingPlanRecord:

[
{
"id": "plan_starter",
"code": "starter",
"name": "Starter",
"description": "For small teams starting with RuleForge.",
"currency": "usd",
"monthly_price_cents": 4900,
"annual_price_cents": 49000,
"minimum_seats": 1,
"maximum_seats": 10,
"status": "active",
"is_public": true,
"contact_sales": false,
"display_order": 1,
"features": {
"api_access": true,
"ai_assistant": false,
"sso": false,
"audit_log_retention_days": 30
},
"limits": {
"projects": 3,
"analysis_runs_per_month": 10000,
"api_requests_per_minute": 120,
"webhooks": 5
},
"metadata": {},
"created_at": "…",
"updated_at": "…"
},
{ "id": "plan_team", "code": "team", "...": "..." },
{ "id": "plan_enterprise", "code": "enterprise", "contact_sales": true, "...": "..." }
]

Plans with contact_sales=true have no public price — use portal or reach out to sales directly to activate.

GET /.../billing

Full billing overview for the organization.

Auth: user session.

Response 200 (OrganizationBillingOverviewResponse):

{
"organization_id": "org_...",
"organization_name": "Acme Security",
"subscription": {
"id": "sub_...",
"organization_id": "org_...",
"plan_id": "plan_team",
"status": "active",
"billing_interval": "month",
"seat_count": 5,
"gateway_provider": "stripe",
"gateway_subscription_id": "sub_Nz…",
"trial_ends_at": null,
"current_period_start": "2026-04-01T00:00:00Z",
"current_period_end": "2026-05-01T00:00:00Z",
"cancel_at": null,
"canceled_at": null,
"created_at": "…",
"updated_at": "…"
},
"plan": { "...": "BillingPlanRecord" },
"available_plans": [ "BillingPlanRecord[]" ],
"usage": [
{
"metric": "analysis_runs",
"period_start": "2026-04-01T00:00:00Z",
"period_end": "2026-05-01T00:00:00Z",
"consumed": 2741,
"limit": 10000
},
{
"metric": "projects",
"consumed": 4,
"limit": 10
}
],
"gateway": {
"provider": "stripe",
"status": "connected",
"publishable_key": "pk_live_..."
}
}
  • subscription.statusunpaid.
  • usage[] — metrics consumed in the current period. limit=null means unlimited.
  • gateway.publishable_key is only populated when the gateway is Stripe.

POST /.../billing/checkout

Creates a checkout session (typically Stripe Checkout) for the user to complete the subscription/upgrade.

Request body (BillingCheckoutRequest):

{
"plan_id": "plan_team",
"billing_interval": "month",
"seat_count": 5
}
  • plan_id — target plan ID.
  • billing_interval"month" or "year".
  • seat_count — 1 to 100,000.

Response 200 (BillingCheckoutResponse):

{
"provider": "stripe",
"url": "https://checkout.stripe.com/c/pay/cs_...",
"session_id": "cs_..."
}

Typical client flow:

  1. Frontend calls this endpoint.
  2. Receives url and redirects the user.
  3. User completes payment on Stripe.
  4. Stripe calls back /billing/webhooks/stripe (not documented here — internal webhook).
  5. RuleForge updates subscription.status to active.
  6. The frontend receives ?checkout=success (configurable via NATIVE_WAZUH_BILLING_SUCCESS_PATH).

Errors:

  • 400 bad_request — plan doesn't exist or is not public.
  • 402 payment_required — gateway unconfigured or unavailable.
  • 403 forbidden — organization in canceled state.

POST /.../billing/portal

Creates a URL for the customer portal (Stripe Customer Portal), where the user can:

  • View invoice history.
  • Update payment method.
  • Cancel subscription.
  • Downgrade.

Request body (BillingPortalRequest):

{ "return_path": "/settings/billing" }
  • return_path — absolute path (starts with /) to return to after leaving the portal.

Response 200 (BillingPortalResponse):

{
"provider": "stripe",
"url": "https://billing.stripe.com/p/session/bps_..."
}

Client redirects the user to url.

Stripe internal webhook

POST /billing/webhooks/stripe receives Stripe events (invoice.paid, customer.subscription.updated, etc.) to keep local state in sync. Authentication is via the Stripe-Signature signature verified with NATIVE_WAZUH_STRIPE_WEBHOOK_SECRET.

You don't need to call this endpoint — it's configured in the Stripe dashboard by the platform operator.

Shared structures

BillingPlanRecord

See the example above at GET /billing/plans/public.

OrganizationSubscriptionRecord

{
"id": "string",
"organization_id": "string",
"plan_id": "string",
"status": "trialing|active|past_due|canceled|incomplete|incomplete_expired|unpaid",
"billing_interval": "month|year",
"seat_count": 1,
"gateway_provider": "string",
"gateway_subscription_id": "string|null",
"trial_ends_at": "ISO 8601|null",
"current_period_start": "ISO 8601|null",
"current_period_end": "ISO 8601|null",
"cancel_at": "ISO 8601|null",
"canceled_at": "ISO 8601|null",
"created_at": "ISO 8601",
"updated_at": "ISO 8601"
}

BillingUsageMetric

{
"metric": "string",
"period_start": "ISO 8601",
"period_end": "ISO 8601",
"consumed": 0,
"limit": 0
}

Common metrics: analysis_runs, projects, api_requests, webhooks, members.

codeWhen
plan_quota_exceededSome operation hits the plan limit (analysis_runs, webhooks, etc.).
billing_gateway_disabledNATIVE_WAZUH_BILLING_GATEWAY=disabled.
billing_subscription_requiredOperation requires an active plan and the organization has no valid subscription.