Skip to content

Webhook Delivery Guarantees

Delivery Model

At-least-once delivery

Invora guarantees every webhook event is delivered at least once. Events may be delivered more than once during retries or network issues. Your endpoint must be idempotent.

Idempotency

Every webhook event includes a unique webhook_type + document ID combination. Use this as an idempotency key:

Key = "{webhook_type}:{id}:{created_at}"

Store processed keys and skip duplicates. Events are immutable — the same event ID always carries the same payload.

Retry Policy

Failed deliveries (non-2xx response or timeout) are retried with exponential backoff:

Attempt Delay Total elapsed
1 Immediate 0s
2 30s 30s
3 2min 2.5min
4 15min 17.5min
5 1h 1h 17min
6 6h 7h 17min

After 6 failed attempts, the event is marked FAILED. You can manually retry via RetryWebhook RPC.

Timeout

Your endpoint must respond within 15 seconds. Slow endpoints cause retries. If your processing takes longer, accept the webhook immediately (return 200) and process asynchronously.

Ordering

No ordering guarantee

Events for the same document may arrive out of order, especially during retries. Always check the event's created_at timestamp and ignore events older than your last-processed timestamp for that resource.

Example: invoice.finalized might arrive after invoice.payment_status_updated if the first delivery failed and was retried.

Signature Verification

Every webhook request includes a signature for authenticity verification. Two algorithms are supported:

Algorithm Header How to Verify
HMAC X-Invora-Signature HMAC-SHA256 of raw body with your endpoint's secret
JWT X-Invora-Signature JWT signed with RS256; verify with Invora's public key from JWKS endpoint

Configure the algorithm when creating the webhook endpoint:

curl -X POST https://gateway.invora.app/api/billing/v2/webhook-endpoints \
  -d '{
    "webhookUrl": "https://your-app.com/webhooks/invora",
    "eventTypes": ["INVOICE_CREATED", "INVOICE_PAYMENT_STATUS_UPDATED"],
    "signatureAlgo": "HMAC"
  }'

HMAC Verification (Node.js)

const crypto = require('crypto');

function verifyWebhook(body, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body, 'utf8')
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Event Types

Commonly used subscribable events:

Event types are proto enum values from EventType. In JSON transcoding, use the string name without the EVENT_TYPE_ prefix (e.g., "INVOICE_CREATED").

The selection below covers the most frequently used events. The complete set is the EventType enum in billing/invora/billing/webhooks/v2/service.proto (59 fireable events). Subscribing to ALL (EVENT_TYPE_ALL) delivers every event without listing them individually.

Invoice Events

  • INVOICE_CREATED — Draft invoice created
  • INVOICE_DRAFTED — Invoice moved to draft state
  • INVOICE_GENERATED — Invoice PDF generated
  • INVOICE_ONE_OFF_CREATED — One-off invoice created
  • INVOICE_VOIDED — Invoice voided
  • INVOICE_PAYMENT_STATUS_UPDATED — Payment status changed
  • INVOICE_PAYMENT_FAILURE — Payment attempt failed
  • INVOICE_PAYMENT_OVERDUE — Invoice past due date
  • INVOICE_PAYMENT_DISPUTE_LOST — Payment dispute resolved against merchant
  • INVOICE_PAID_CREDIT_ADDED — Paid credit applied to invoice
  • INVOICE_RESYNCED — Invoice re-synced with integration

Subscription Events

  • SUBSCRIPTION_STARTED — New subscription activated
  • SUBSCRIPTION_TERMINATED — Subscription ended
  • SUBSCRIPTION_UPDATED — Subscription modified
  • SUBSCRIPTION_TRIAL_ENDED — Free trial expired
  • SUBSCRIPTION_TERMINATION_ALERT — Upcoming termination warning
  • SUBSCRIPTION_USAGE_THRESHOLD_REACHED — Usage threshold hit

Payment Events

  • PAYMENT_SUCCEEDED — Payment confirmed
  • PAYMENT_REQUIRES_ACTION — 3DS or additional auth required
  • PAYMENT_RECEIPT_CREATED — Payment receipt generated
  • PAYMENT_RECEIPT_GENERATED — Payment receipt PDF ready
  • PAYMENT_REQUEST_CREATED — Payment request sent
  • PAYMENT_REQUEST_PAYMENT_FAILURE — Payment request payment failed
  • PAYMENT_REQUEST_PAYMENT_STATUS_UPDATED — Payment request status changed

Customer Events

  • CUSTOMER_CREATED — Billing customer created
  • CUSTOMER_UPDATED — Customer details changed
  • CUSTOMER_CHECKOUT_URL_GENERATED — Checkout URL ready
  • CUSTOMER_PAYMENT_PROVIDER_CREATED — Payment provider linked
  • CUSTOMER_VIES_CHECK — VAT number VIES validation completed

Credit Note Events

  • CREDIT_NOTE_CREATED — Credit note issued
  • CREDIT_NOTE_GENERATED — Credit note PDF ready
  • CREDIT_NOTE_PROVIDER_REFUND_FAILURE — Refund via provider failed

Wallet Events

  • WALLET_CREATED — Prepaid wallet created
  • WALLET_DEPLETED_ONGOING_BALANCE — Wallet ongoing balance depleted
  • WALLET_TERMINATED — Wallet closed
  • WALLET_UPDATED — Wallet details changed
  • WALLET_TRANSACTION_CREATED — Wallet transaction recorded
  • WALLET_TRANSACTION_UPDATED — Wallet transaction updated
  • WALLET_TRANSACTION_PAYMENT_FAILURE — Wallet top-up payment failed

Other Events

  • ALERT_TRIGGERED — Billing alert threshold reached
  • FEE_CREATED — Fee generated
  • PLAN_CREATED / PLAN_UPDATED / PLAN_DELETED — Plan lifecycle
  • FEATURE_CREATED / FEATURE_UPDATED / FEATURE_DELETED — Entitlement feature lifecycle
  • DUNNING_CAMPAIGN_FINISHED — Dunning campaign completed
  • EVENT_ERROR / EVENTS_ERRORS — Event ingestion error

Testing Webhooks

In staging (stg-gateway.invora.app), use the webhook list/retry RPCs to replay events:

# List recent webhook deliveries
grpcurl -d '{"webhookEndpointId": "ep-123"}' \
  stg-gateway.invora.app:443 invora.billing.webhooks.v2.WebhookEndpointsService/Webhooks

# Retry a specific failed webhook
grpcurl -d '{"input": {"id": "wh-456"}}' \
  stg-gateway.invora.app:443 invora.billing.webhooks.v2.WebhookEndpointsService/RetryWebhook