Skip to main content

Webhooks

The Equa platform uses webhooks for both receiving third-party billing events and (proposed) sending outbound notifications when resources change. This page documents both systems.

Inbound Webhooks (Chargify)

The Equa API receives webhooks from Chargify (Maxio) to keep billing and subscription data in sync.
Source: equa-server/modules/api/src/endpoints/billing-endpoints.ts

Events Handled

How It Works

  1. An event occurs in Chargify (e.g., subscription state change)
  2. Chargify sends an HTTP POST to the configured webhook URL on the Equa API
  3. The billing module processes the event and updates the local database
  4. Subscription features and limits are recalculated for the affected organization

Subscription Synchronization

The billing system automatically syncs member/shareholder counts with Chargify whenever cap table changes occur. This ensures billing stays aligned with actual usage:
  • When a shareholding is created or deleted, the shareholder count is updated via appendShareholderCountUpdateViaShareholding (Source: captable-endpoints.ts, line 138)
  • When a holding (option grant) is created or deleted, the member count is updated via updateChargifyMemberCount (Source: captable-endpoints.ts, line 139)
  • These count changes trigger Chargify component quantity updates

Webhook Security

Webhook payloads from Chargify are validated server-side to ensure they originate from Chargify. Only events from authenticated Chargify accounts tied to the configured Chargify API credentials are processed.

Configuring Chargify Webhooks

Webhook URLs are configured in the Chargify dashboard and correspond to the billing endpoints on the Equa API server. These are internal server-to-server integrations and are not exposed for external consumption. If you need to integrate with Equa billing events in your application, use the Billing Endpoints to query subscription and transaction data instead.

Outbound Webhooks (Proposed)

The outbound webhook system is a proposed design specification. It is not yet implemented in the Equa API. This documentation describes the planned architecture for when the feature is built. All endpoints, payloads, and behaviors described in this section are subject to change.

Overview

The proposed outbound webhook system allows integrators to receive real-time HTTP notifications when resources change within the Equa platform. The design follows the Stripe webhook model — a widely adopted pattern for financial SaaS APIs. Key capabilities:
  • Event-driven: Receive HTTP POST callbacks when cap table, organization, document, or billing events occur
  • Secure: HMAC-SHA256 signatures on every payload for verification and replay protection
  • Reliable: Automatic retries with exponential backoff over a 26-hour window
  • Filterable: Subscribe to specific event types per webhook endpoint
  • Idempotent: Unique event IDs enable safe deduplication by consumers

Event Types

Events use {resource}.{action} dot-notation. Subscribe to all events or filter by specific types.

Cap Table Events

These events fire when equity instruments and related records change within an organization’s cap table.

Organization Events

These events fire when organization-level resources change.

Document Events

These events fire when documents and folders are managed within an organization.

Subscription Events

These events fire for billing lifecycle changes. They complement the inbound Chargify webhooks by providing a normalized outbound notification format.

Event Type Summary

Payload Format

Every webhook delivery uses a consistent JSON envelope:

Envelope Fields

Example Payloads

shareholding.created — New shares issued:
member.updated — Member role changed:
holding.converted — Convertible note converted to equity:
payment.failed — Payment failure notification:

Delivery and Retries

Transport

  • Webhooks are delivered as HTTP POST requests to the registered endpoint URL
  • The request body is JSON (Content-Type: application/json)
  • The registered URL must be HTTPS in production environments
  • Each delivery includes the Equa-Signature header for verification (see Signature Verification)

Timeout

Each delivery attempt has a 30-second timeout. If the endpoint does not respond with an HTTP 2xx status code within 30 seconds, the attempt is considered failed.

Retry Schedule

Failed deliveries are retried with exponential backoff: After 6 total attempts (1 initial + 5 retries) over approximately 26 hours, the event is marked as failed. Webhook endpoints that consistently fail will be automatically disabled after 3 consecutive days of failures.
A delivery is considered successful when the endpoint returns any HTTP 2xx status code (200, 201, 202, etc.). Any other status code — including 3xx redirects — triggers a retry. Return 200 as quickly as possible; perform heavy processing asynchronously.

Idempotency

Every event has a unique id field (e.g., evt_2f8a3b1c4d5e6f7890abcdef). The same event may be delivered more than once due to retries or network issues. Consumers must store processed event IDs and skip duplicates:

Ordering

Events are delivered in best-effort chronological order based on the created timestamp. However, due to retry scheduling and network variability, events may arrive out of order. Consumers should:
  1. Use the created timestamp to determine actual event order
  2. Handle out-of-order delivery gracefully (e.g., ignore a shareholding.updated that arrives before the corresponding shareholding.created)
  3. Fetch the current resource state via the API if ordering is critical

Signature Verification

Every webhook delivery includes an Equa-Signature header that allows the consumer to verify the payload originated from Equa and has not been tampered with.

Header Format

The header contains two comma-separated key-value pairs:

Verification Steps

  1. Extract the t (timestamp) and v1 (signature) values from the Equa-Signature header
  2. Construct the signed payload string: {timestamp}.{raw_request_body} (the t value, a literal ., and the raw JSON body — do not parse and re-serialize)
  3. Compute the expected signature: HMAC-SHA256(signing_secret, signed_payload) and hex-encode the result
  4. Compare the computed signature with the v1 value using a constant-time comparison
  5. Check the timestamp is within 5 minutes of the current time to prevent replay attacks

Signing Secret

Each registered webhook endpoint receives a unique signing secret (prefixed whsec_) at creation time. The secret is displayed once and should be stored securely. If compromised, delete the webhook and create a new one.

Timestamp Tolerance

Reject any webhook where the t timestamp differs from the current server time by more than 5 minutes (300 seconds). This prevents replay attacks where a captured payload is re-sent at a later time.

Webhook Management Endpoints

These endpoints are proposed and not yet implemented. They describe the planned API for managing outbound webhook registrations.

Register a Webhook

Creates a new webhook endpoint registration for the organization. Permission: canEditOrganization Request Body: Example Request:
Response (201 Created):
The secret field is returned only on creation. Store it securely — it cannot be retrieved again. If lost, delete the webhook and create a new one.

List Webhooks

Returns all registered webhook endpoints for the organization. Permission: canViewOrganization Response (200 OK):
The secret field is never returned in list or get responses. It is only available at creation time.

Delete a Webhook

Permanently removes a webhook registration. Pending retries for this webhook are cancelled. Permission: canEditOrganization Response (204 No Content): Empty body.

Send a Test Event

Sends a synthetic test event to the registered URL. Useful for verifying connectivity and signature verification during integration. Permission: canEditOrganization Request Body: Response (200 OK):
If the test delivery fails:

Example Integration

The following Node.js/Express example shows how to receive, verify, and process Equa webhook events.
Key implementation notes:
  • Use express.raw() (not express.json()) so you receive the raw bytes for signature verification. Parsing and re-serializing JSON may alter whitespace, breaking the signature.
  • Always use crypto.timingSafeEqual() for signature comparison to prevent timing attacks.
  • Return 200 immediately and queue heavy processing for async workers. The 30-second delivery timeout is strict.
  • Store processed event IDs in a persistent store (database, Redis) with a TTL of at least 48 hours to handle retry deduplication across the full retry window.

Best Practices

  1. Use HTTPS endpoints. Webhook payloads may contain sensitive financial data (share counts, member details, payment information). Always use TLS-encrypted endpoints.
  2. Respond quickly. Return a 2xx response within a few seconds. Offload processing to a background queue (e.g., Bull, SQS, Pub/Sub) to avoid timeouts.
  3. Implement idempotency. Due to retries, you may receive the same event more than once. Track processed event IDs and skip duplicates.
  4. Handle out-of-order events. Use the created timestamp and the current resource state (via API calls) rather than assuming delivery order matches event order.
  5. Monitor delivery health. Use the List Webhooks endpoint to check lastDelivery status. Set up alerts for consecutive failures.
  6. Rotate secrets periodically. Delete and recreate webhook registrations to rotate signing secrets. Update your verification code before deleting the old webhook.
  7. Filter events at registration. Subscribe only to the event types you need. This reduces delivery volume and the surface area for processing errors.
  8. Log raw payloads. Store the raw JSON body and signature header for debugging. This helps diagnose verification failures and unexpected payload formats.