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:
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 createdINVOICE_DRAFTED— Invoice moved to draft stateINVOICE_GENERATED— Invoice PDF generatedINVOICE_ONE_OFF_CREATED— One-off invoice createdINVOICE_VOIDED— Invoice voidedINVOICE_PAYMENT_STATUS_UPDATED— Payment status changedINVOICE_PAYMENT_FAILURE— Payment attempt failedINVOICE_PAYMENT_OVERDUE— Invoice past due dateINVOICE_PAYMENT_DISPUTE_LOST— Payment dispute resolved against merchantINVOICE_PAID_CREDIT_ADDED— Paid credit applied to invoiceINVOICE_RESYNCED— Invoice re-synced with integration
Subscription Events¶
SUBSCRIPTION_STARTED— New subscription activatedSUBSCRIPTION_TERMINATED— Subscription endedSUBSCRIPTION_UPDATED— Subscription modifiedSUBSCRIPTION_TRIAL_ENDED— Free trial expiredSUBSCRIPTION_TERMINATION_ALERT— Upcoming termination warningSUBSCRIPTION_USAGE_THRESHOLD_REACHED— Usage threshold hit
Payment Events¶
PAYMENT_SUCCEEDED— Payment confirmedPAYMENT_REQUIRES_ACTION— 3DS or additional auth requiredPAYMENT_RECEIPT_CREATED— Payment receipt generatedPAYMENT_RECEIPT_GENERATED— Payment receipt PDF readyPAYMENT_REQUEST_CREATED— Payment request sentPAYMENT_REQUEST_PAYMENT_FAILURE— Payment request payment failedPAYMENT_REQUEST_PAYMENT_STATUS_UPDATED— Payment request status changed
Customer Events¶
CUSTOMER_CREATED— Billing customer createdCUSTOMER_UPDATED— Customer details changedCUSTOMER_CHECKOUT_URL_GENERATED— Checkout URL readyCUSTOMER_PAYMENT_PROVIDER_CREATED— Payment provider linkedCUSTOMER_VIES_CHECK— VAT number VIES validation completed
Credit Note Events¶
CREDIT_NOTE_CREATED— Credit note issuedCREDIT_NOTE_GENERATED— Credit note PDF readyCREDIT_NOTE_PROVIDER_REFUND_FAILURE— Refund via provider failed
Wallet Events¶
WALLET_CREATED— Prepaid wallet createdWALLET_DEPLETED_ONGOING_BALANCE— Wallet ongoing balance depletedWALLET_TERMINATED— Wallet closedWALLET_UPDATED— Wallet details changedWALLET_TRANSACTION_CREATED— Wallet transaction recordedWALLET_TRANSACTION_UPDATED— Wallet transaction updatedWALLET_TRANSACTION_PAYMENT_FAILURE— Wallet top-up payment failed
Other Events¶
ALERT_TRIGGERED— Billing alert threshold reachedFEE_CREATED— Fee generatedPLAN_CREATED/PLAN_UPDATED/PLAN_DELETED— Plan lifecycleFEATURE_CREATED/FEATURE_UPDATED/FEATURE_DELETED— Entitlement feature lifecycleDUNNING_CAMPAIGN_FINISHED— Dunning campaign completedEVENT_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