ISV credit webhooks
⚠️ Internal sandbox preview — not approved for partner or production use. Credit reads and writes do not yet share one environment model. Do not integrate against production credit data until Complyance confirms the environment contract.
Credit activity is published as webhook events so your system can react without polling. Register a webhook subscription for your workspace with a webhookUrl, the list of events to receive, and an optional secretKey for signing. Owners, Admins, and Partners can manage credit webhook subscriptions. Subscriptions are matched per workspace, per event type, and per data environment.
Supported events
| Event | Trigger |
|---|---|
isv.credits.allowance.changed | A current or scheduled allowance was created, replaced, cancelled, activated, or superseded. |
isv.credits.allowance.paused | A current allowance was paused. |
isv.credits.allowance.resumed | A paused allowance was resumed. |
isv.credits.allowance.expired | An allowance period ended. |
isv.credits.company.threshold | A company's committed usage (finalized plus reserved) crossed 80% of its current allowance. |
isv.credits.company.exhausted | A company's available allowance reached zero. |
isv.credits.pool.low | The available ISV pool crossed a low threshold. Emitted once per crossed threshold at 20% and at 10% remaining. |
isv.credits.pool.exhausted | The available ISV pool reached zero. |
isv.credits.consumption.reversed | A finalized consumption was reversed. |
isv.credits.bulk.completed | A bulk allowance job finished, with or without row errors. |
Threshold behaviour
company.thresholdfires when committed usage crosses from below 80% to 80% or more of the current allowance. The payload carriesthresholdPercent: 80.company.exhaustedfires when the company's available allowance goes from above zero to exactly zero, and takes precedence over the threshold event.pool.lowfires once per crossed threshold — at 20% and at 10% of pool credits remaining. Each threshold has its own event identity, so one consumption that crosses both emits two events, each carrying itsthresholdPercent.pool.exhaustedfires when the pool reaches exactly zero and takes precedence overpool.low.
Delivery format
Events are delivered as an HTTP POST to your subscription URL:
| Header | Description |
|---|---|
Content-Type | application/json |
X-Webhook-ID | The unique event ID. Use it for deduplication. |
X-Webhook-Event | The event type, for example isv.credits.pool.low. |
X-Webhook-Timestamp | Delivery timestamp in ISO 8601 format. |
X-Webhook-Signature | Present when the subscription has a secretKey: hex HMAC (SHA-256 by default, SHA-512 supported) of the raw request body. Verify it before trusting the payload. |
Request body envelope:
{
"eventId": "isv-credit:0f6c…",
"data": {
"id": "isv-credit:0f6c…",
"type": "isv.credits.allowance.changed",
"timestamp": "2026-08-12T10:00:00.000Z",
"data": {}
}
}The inner data object is the event payload. Every credit payload carries schemaVersion (currently "1.0").
Event payloads
Allowance lifecycle events (allowance.changed, allowance.paused, allowance.resumed, allowance.expired):
{
"schemaVersion": "1.0",
"companyId": "{COMPANY_ID}",
"action": "current.replaced",
"allowance": {
"target": "current",
"limit": 1000,
"periodType": "monthly",
"allowancePeriodId": "period_01",
"startAt": "2026-08-01T00:00:00.000Z",
"endAt": "2026-09-01T00:00:00.000Z",
"status": "active",
"version": 4,
"finalizedUsage": 250,
"reserved": 20
},
"reason": "Plan upgrade",
"bulkJobId": "{JOB_ID}"
}action is one of current.created, current.replaced, current.paused, current.resumed, current.expired, scheduled.created, scheduled.replaced, scheduled.cancelled, scheduled.activated, or scheduled.expired. bulkJobId is present only when the change came from a bulk job.
Company capacity events (company.threshold, company.exhausted):
{
"schemaVersion": "1.0",
"companyId": "{COMPANY_ID}",
"allowancePeriodId": "period_01",
"reservationId": "res_01",
"limit": 1000,
"finalizedUsage": 700,
"reserved": 100,
"committedUsage": 800,
"available": 200,
"percentageUsed": 80,
"thresholdPercent": 80,
"amount": 10
}thresholdPercent is present on company.threshold only.
Pool capacity events (pool.low, pool.exhausted):
{
"schemaVersion": "1.0",
"triggeringCompanyId": "{COMPANY_ID}",
"reservationId": "res_01",
"totalCredits": 10000,
"availableCredits": 1900,
"percentageAvailable": 19,
"thresholdPercent": 20,
"amount": 25
}thresholdPercent is present on pool.low only.
consumption.reversed:
{
"schemaVersion": "1.0",
"companyId": "{COMPANY_ID}",
"reservationId": "res_01",
"transactionId": "txn_02",
"originalTransactionId": "txn_01",
"allowancePeriodId": "period_01",
"amount": 1,
"reason": "Document rejected",
"restorePool": true,
"countryCode": "AE",
"documentType": "invoice",
"balanceBefore": { "companyAvailable": 730, "poolAvailable": 6800 },
"balanceAfter": { "companyAvailable": 731, "poolAvailable": 6801 }
}bulk.completed:
{
"schemaVersion": "1.0",
"jobId": "{JOB_ID}",
"status": "completed_with_errors",
"totals": {
"total": 2,
"processed": 2,
"succeeded": 1,
"failed": 1
},
"completedAt": "2026-08-12T10:05:00.000Z"
}Versioning
Event type names are stable identifiers. Payloads carry schemaVersion, currently "1.0". New optional fields may be added without a version change; breaking payload changes increment schemaVersion. Ignore unknown fields.
Deduplication
Delivery is at-least-once. The event ID is deterministic for the underlying occurrence, so an idempotent replay of the same write produces the same ID, and retries redeliver the same ID. Deduplicate on eventId (also sent as X-Webhook-ID). For pool.low, the crossed threshold is part of the event identity, so the 20% and 10% notifications are distinct events that do not deduplicate each other.
Environment
Each event is produced in the data environment of the operation that caused it and is delivered only to subscriptions for that environment. Until the credit API environment model is unified, treat webhook traffic with the same caution as the write APIs.
Retries and failure handling
Each subscription has independent delivery state. A delivery succeeds on any 2xx response. A 4xx response is treated as permanent and is not retried. 5xx responses, timeouts, and network failures are retried up to 5 attempts per subscription, with delays of 60 seconds, then 5 minutes, then 15 minutes between the remaining attempts. After the final attempt, the delivery is marked failed and is not redelivered.
Respond 2xx quickly and process asynchronously. Events can arrive out of order — the delivery pipeline does not guarantee ordering — so order by the payload timestamp (and allowance.version for allowance events) rather than arrival order.
Replay and reconciliation
There is no partner-facing replay API. If deliveries were missed, reconcile from the authoritative credit ledger using GET /v3/connect/credits/transactions or a ledger export. Event and delivery records are retained internally by Complyance for troubleshooting; contact support with the eventId when investigating a delivery.