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 path | Purpose |
|---|---|
GET /v1/webhooks | List 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}/deliveries | Inspect 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.