Documentation
Browse documentation
Language
EN ES

Receiving events with webhooks

Receive signed invoice and compliance updates without polling the API.
View as Markdown

Webhooks notify your integration after something important happens in a Live or Test account. They are especially useful for asynchronous Tax ID verification, VERI*FACTU outcomes, and balance movements, but can also report customer, invoice, amendment and PDF activity.

Create a Test destination

Create the destination with a Test API key so experiments cannot mix with Live events. Subscribe only to events your integration understands; use "*" only when you deliberately want every current and future event type.

curl https://api.fiscalrail.com/v1/event-destinations \
  --request POST \
  --header "Authorization: Bearer ak_test_..." \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Invoice updates",
    "url": "https://example.com/webhooks/fiscalrail",
    "enabled_events": [
      "invoice.amended",
      "invoice.verifactu_registration.accepted",
      "invoice.verifactu_registration.accepted_with_errors",
      "invoice.verifactu_registration.rejected"
    ]
  }'

The response contains a whsec_... signing secret. Store it securely. FiscalRail can return it when retrieving that destination, but list responses omit it.

Your endpoint must use public HTTPS. For local development, expose the handler through a trusted HTTPS tunnel and replace the destination URL when it changes.

Verify before parsing

FiscalRail signs the exact raw request body and sends the result in FiscalRail-Signature. Verify the signature before parsing JSON or starting work.

import hashlib
import hmac
import time


def verify_fiscalrail_signature(raw_body, header, secret):
    parts = dict(item.split("=", 1) for item in header.split(","))
    timestamp = int(parts["t"])
    if abs(time.time() - timestamp) > 300:
        return False

    signed = str(timestamp).encode() + b"." + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

The signature covers timestamp + "." + raw_body using HMAC-SHA256. Reject timestamps more than five minutes old and compare signatures in constant time. Every retry receives a new timestamp and signature.

Acknowledge quickly

Return any 2xx response as soon as the event has been authenticated and durably queued. Do slow work after acknowledging it.

Deliveries are at least once and are not ordered. Store the Event ID before processing and ignore IDs you have already handled. Do not assume invoice.issued arrives before invoice.amended, or that a VERI*FACTU result arrives immediately after issuance.

Webhook payloads are intentionally thin. They identify the Event and related resource but omit the immutable data snapshot. Retrieve the Event by ID when you need the historical snapshot, or retrieve the related resource when you need its current state.

Subscribe a Live destination to billing.balance_transaction.created to reconcile usage debits and top-up credits. Retrieve the full Event to read the immutable Balance Transaction snapshot. A usage transaction's source_event identifies the successful domain Event that caused the debit.

Exercise the complete flow

Issue or correct an invoice in the same Test account. Your endpoint should receive the subscribed event. Confirm that your handler:

  1. verifies the raw body and timestamp;
  2. records the Event ID exactly once;
  3. returns 2xx promptly;
  4. retrieves the Event or related invoice asynchronously;
  5. safely ignores event types it does not understand.

FiscalRail attempts a failed delivery up to five times. If every attempted delivery keeps failing for 24 hours, the destination is disabled. Fix the endpoint and enable the destination again through the API or dashboard.

See the Events reference for the event envelope and the Webhooks reference for destination fields and delivery rules.