Webhooks

Subscribe to events and receive signed HTTP notifications.


Webhooks send an HTTP request to your application when an event occurs in Finbar. They are available to paid accounts with public API access. The only supported event type is currently document.published.

Subscriptions belong to your billing account. Authorized users and API clients on that account can manage them. Each account can have up to 1,000 subscriptions. Delivery has no additional charge and does not use your document-read quota.

Create a subscription

Use an access token with the webhooks:manage and documents:read scopes. If your credential lacks either scope, create a replacement REST credential; rotating a credential does not add scopes.

Provide the entity to watch and the URL that will receive events. Each subscription currently receives document.published events for one entity.

curl --fail-with-body --silent --show-error \
  'https://api.finbar.com/v1/webhooks' \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --header 'Content-Type: application/json' \
  --data '{"entity_id":"O-YOUR_ENTITY_ID","url":"https://your.example/finbar-events"}'

The 201 response contains id, entity_id, url, created_at, and signing_secret. Save the signing secret when you create the subscription; it cannot be retrieved later. It is a base64-encoded 32-byte key. Keep it out of logs, source control, and callback URLs.

To change the callback URL or replace a lost secret, delete the subscription and create another. Creation does not support Idempotency-Key. If the response is lost, list existing subscriptions before trying again.

The callback must use public HTTPS on port 443. Private addresses, embedded URL credentials, and URL fragments are rejected. Finbar does not follow redirects.

Event types

document.published

Sent when a document first finishes processing and is ready to read. The subscription covers every document type for the selected entity. Documents already ready when you subscribe are excluded. Reprocessing and metadata corrections do not produce another event.

Email alerts and webhooks both exclude documents with a reporting year before 2023, or at least two years behind the latest available document for that entity. For example, if the latest reporting year is 2026, documents from 2024 and earlier do not trigger notifications. The comparison includes all document types and uses the reporting period year, falling back to the report date when no period year is set. Documents without a usable year are excluded.

This check also applies before delivery retries. A queued notification can be cancelled if newer documents make it too old.

Example payload:

{
  "id": "962410e0-738c-4479-b90b-85674cc3b350",
  "type": "document.published",
  "occurred_at": "2026-10-28T12:00:00Z",
  "subscription_id": "53948a28-5a7b-4396-84b1-3e4b87d36013",
  "document": {
    "document_id": "D-YOUR_DOCUMENT_ID",
    "entity_id": "O-YOUR_ENTITY_ID",
    "period": {"year": 2026, "period_type": "Q", "period": 3},
    "publish_date": "2026-10-28T00:00:00Z",
    "report_date": "2026-10-28T00:00:00Z",
    "status": "PARSED",
    "type": "FULL_FINANCIAL_REPORT"
  }
}

The document object uses the same metadata format as the document list API. Use document_id to read the document. The event contains metadata only, without the document text or storage URLs.

Verify signatures

Requests include:

  • Webhook-Id: the event ID, unchanged across retries.
  • Webhook-Timestamp: Unix seconds for this delivery attempt.
  • Webhook-Signature: v1= followed by a hexadecimal HMAC-SHA256 digest.

Base64-decode the signing secret and use it as the HMAC key. Sign the exact bytes of Webhook-Id + "." + Webhook-Timestamp + "." + raw_request_body. Use the unmodified request body, before parsing JSON. Compare signatures in constant time and reject timestamps more than five minutes from your clock. Keep clocks synchronized. Each retry has a fresh timestamp and signature.

import base64
import hashlib
import hmac
import time

def verify(secret, event_id, timestamp, signature, raw_body):
    try:
        if abs(time.time() - int(timestamp)) > 300:
            return False
        message = f"{event_id}.{timestamp}.".encode() + raw_body
        expected = "v1=" + hmac.new(
            base64.b64decode(secret, validate=True), message, hashlib.sha256
        ).hexdigest()
        return hmac.compare_digest(expected, signature)
    except (ValueError, TypeError):
        return False

Delivery and retries

Finbar aims to start delivery within a few seconds of an event. Events may arrive out of order or more than once. Check the signature, save the event to a durable queue, and return a 2xx response. Process the queued event separately. Use (subscription_id, id) to avoid processing the same event twice.

Requests time out after 10 seconds. Finbar retries network failures and non-2xx responses for up to 24 hours after queueing the delivery. Retry delays increase to a maximum of one hour and vary to spread requests over time. When the retry period ends, the delivery is marked failed. Later events are still sent. There is no manual replay endpoint.

Manage subscriptions

Management endpoints require webhooks:manage. Any authorized user or client on the billing account can list, inspect, or delete its subscriptions and read their delivery history.

Method and pathPurpose
GET /v1/webhooksList account subscriptions
GET /v1/webhooks/{webhook_id}Inspect a subscription
DELETE /v1/webhooks/{webhook_id}Stop future and queued deliveries
GET /v1/webhooks/{webhook_id}/deliveriesInspect 30 days of delivery history

Lists return items and, when another page is available, next_cursor. Pass that value as cursor to fetch the next page. The limit parameter defaults to 50 and accepts values from 1 to 100. Management requests share an account limit of 60 per minute, with a burst of 10.

Delivery history includes status, attempt count, timestamps, the last HTTP status, and a failure code. It excludes response bodies and signing secrets. Delivered, failed, and cancelled delivery records are removed once they are 30 days old.

Stop delivery

Delete a subscription to stop an integration. Rotating or revoking the credential that created it does not stop delivery. Subscriptions also continue if the creating user leaves the team.

Delivery stops if the account loses its paid plan or public API access. When access is restored, delivery resumes for future events; events from the interruption are not replayed. Accounts that retain API access can still inspect and delete subscriptions after a downgrade.

A request already in progress may complete after a subscription is deleted or account access is lost.