RanglerDeveloper
Integrate

Webhooks

Deliver Rangler company, filing, fund, meeting, and Connect events to your systems with signed webhook requests.

Webhooks deliver matching Rangler events to your systems.

Use webhooks as the default way to receive product events. Use event feeds to load earlier events and check for missed deliveries.

Delivery model

Rangler sends HTTPS POST requests to every active webhook endpoint in your organization that matches at least one active event subscription rule.

The portal supports multiple active endpoints per organization. Rangler evaluates subscriptions at the organization level, then fans matching events out to each active endpoint for that organization.

The portal exposes the full current market-event catalog, including filings, financial results, corporate actions, board changes, company meetings and streams, and fund updates. Connect has a separate operational event subscription contract for connection and synchronization lifecycle events.

The payload follows the webhook event format documented in Events.

Send a managed test event

Create an endpoint in the portal, then send a test event from its endpoint actions. You can make the same request through the portal API:

POST /developer/v1/organizations/{org_id}/event-destinations/{destination_id}/test-events
Authorization: Bearer <portal_token>
Content-Type: application/json

{"event_type":"filing.new"}

The request returns 202 Accepted after Rangler queues the event with the managed delivery service. Test events appear in endpoint delivery history, so you can inspect status, response code, latency, and failure classification.

Supported managed test types are currently filing.new and fund.disclosure.updated. They validate transport and signing; they are not inserted into the public /v1/events feed.

Example test payload:

{
  "id": "2b2dc6f4-48aa-4fd7-9d0a-6c96b9f1e4ea",
  "type": "filing.new",
  "occurred_at": "2026-04-11T11:05:12.193927Z",
  "organization_id": "6a4ce6d5-1a8f-4cb3-b5cc-070d46fe6f94",
  "company_id": null,
  "fund_id": null,
  "source_kind": "filing",
  "source_id": "rangler:test:6a4ce6d5-1a8f-4cb3-b5cc-070d46fe6f94:2b2dc6f4-48aa-4fd7-9d0a-6c96b9f1e4ea",
  "title": "Sandbox filing published",
  "summary": "A sandbox filing event was generated from the Rangler portal.",
  "data": {
    "sandbox": true,
    "generated_by": "rangler_portal"
  }
}

Headers

Every webhook delivery includes Webhook-Id, Webhook-Timestamp, and Webhook-Signature.

Webhook-Id participates in signature verification and identifies the managed transport event. The payload's top-level id identifies the logical Rangler event and is the public key for duplicate-processing protection. Do not assume the two values are interchangeable.

Rangler signs the raw request body using:

<webhook_id>.<webhook_timestamp>.<raw_body>

The signature header format is:

v1,<base64_hmac_sha256_digest>

Signature verification examples

Use the raw request body exactly as received.

import crypto from 'node:crypto';

export function verifyRanglerWebhook({
  rawBody,
  secret,
  webhookId,
  webhookTimestamp,
  signature,
  toleranceSeconds = 300,
}: {
  rawBody: Buffer;
  secret: string;
  webhookId: string;
  webhookTimestamp: string;
  signature: string;
  toleranceSeconds?: number;
}) {
  const timestampSeconds = Number(webhookTimestamp);
  if (!Number.isInteger(timestampSeconds)) return false;
  if (Math.abs(Date.now() / 1000 - timestampSeconds) > toleranceSeconds) return false;

  const signedPayload = Buffer.concat([
    Buffer.from(webhookId, 'utf8'),
    Buffer.from('.', 'utf8'),
    Buffer.from(webhookTimestamp, 'utf8'),
    Buffer.from('.', 'utf8'),
    rawBody,
  ]);

  const digest = crypto.createHmac('sha256', decodeSecret(secret)).update(signedPayload).digest('base64');
  const expected = Buffer.from(`v1,${digest}`);
  const received = Buffer.from(signature);
  return expected.length === received.length && crypto.timingSafeEqual(expected, received);
}

function decodeSecret(secret: string) {
  if (!secret.startsWith('whsec_')) {
    return Buffer.from(secret, 'utf8');
  }
  const encoded = secret.slice('whsec_'.length);
  const padded = encoded + '='.repeat((4 - (encoded.length % 4)) % 4);
  return Buffer.from(padded, 'base64url');
}
import base64
import hmac
from hashlib import sha256
import time


def verify_rangler_webhook(
    *,
    raw_body: bytes,
    secret: str,
    webhook_id: str,
    webhook_timestamp: str,
    signature: str,
    tolerance_seconds: int = 300,
) -> bool:
    try:
        timestamp_seconds = int(webhook_timestamp)
    except ValueError:
        return False
    if abs(time.time() - timestamp_seconds) > tolerance_seconds:
        return False

    signed_payload = b".".join(
        [
            webhook_id.encode("utf-8"),
            webhook_timestamp.encode("utf-8"),
            raw_body,
        ]
    )
    digest = hmac.new(_decode_secret(secret), signed_payload, sha256).digest()
    expected = f"v1,{base64.b64encode(digest).decode('utf-8')}"
    return hmac.compare_digest(expected, signature)


def _decode_secret(secret: str) -> bytes:
    if not secret.startswith("whsec_"):
        return secret.encode("utf-8")
    encoded = secret[len("whsec_") :]
    padded = encoded + ("=" * ((4 - len(encoded) % 4) % 4))
    return base64.urlsafe_b64decode(padded.encode("utf-8"))

The examples reject signatures older or newer than five minutes. Apply the same timestamp check in production to limit replay attacks.

Receiver requirements

Your receiver should accept JSON payloads over HTTPS and use this order of operations:

  1. Read and preserve the raw request body.
  2. Verify Webhook-Signature, including the signed Webhook-Id and Webhook-Timestamp.
  3. Parse the payload and atomically insert its top-level event id into a durable inbox with a uniqueness constraint.
  4. Return 2xx after the inbox record is committed.
  5. Process pending inbox records asynchronously and record their processing state.

If the event id already exists, return 2xx without applying its business effects again. Use a database-backed inbox or another shared durable store in production; an in-memory set cannot coordinate replicas and is erased by restarts.

Retries

Rangler treats any non-2xx response, timeout, or connection failure as a failed attempt.

Webhook delivery is at least once. Managed delivery retries use backoff and do not guarantee ordering. Treat the portal's delivery status and next_attempt_at value as authoritative instead of hard-coding a retry timetable in your integration.

Duplicate delivery is expected behavior, not evidence that Rangler created the underlying event twice. Deduplicate by the payload event id and design business processing so a worker retry cannot repeat an irreversible side effect.

Treat the webhook request as a notification, not as proof that work has completed in your system.

Recent delivery attempts are visible for each endpoint in the Rangler Portal. You can redeliver failed attempts from the portal or through the portal API.

Sandbox testing

Rangler supports managed test events and delivery-attempt inspection from the portal.

See Sandbox Testing for the recommended flow.

Rangler sends a webhook event

The delivery arrives as a signed HTTPS POST request in the documented webhook format.

Your edge receiver verifies the signature

Validate the signature against the raw request body before doing any downstream work.

Commit the event to a durable inbox

Uniquely insert the payload event id and payload. Acknowledge only after that durable write commits; workers can then process the inbox asynchronously.

Fetch any supporting API data

Pull filing, fund, or entity detail if your workflow needs richer context than the webhook payload carries.

Fan out to your application

Send alerts, update workflow state, or trigger internal automations after the event is accepted.

Security expectations

Terminate TLS correctly, verify the Rangler signature on every request, reject requests with invalid signatures, use distinct receiver URLs or endpoint configs for test validation and live traffic, and rotate webhook credentials and API keys through normal operational processes.

On this page