Usage Export for Billing Pass-Through
If you send webhooks to your own customers through Hookbase, you can bill them for the volume they
receive. Model each customer as a webhook application (set
externalId to your customer ID), and Hookbase reports usage per application, per UTC day or month,
for up to 13 months back.
The numbers come from a daily usage rollup that is kept after messages pass their retention period. A free-plan message is deleted after 7 days, but its usage still counts in last month's export.
In the dashboard
Open Outbound → Usage. Pick a period (this month, last month, the last 3 or 12 full months, or a custom UTC range), group by month or day, and optionally filter to one application. Download CSV exports the whole range, every application and period, not only the rows on screen.
Metrics
Every metric is attributed to the UTC calendar day on which it happened. Monthly figures are the sum of their days.
| Metric | Field (JSON / CSV) | What is counted |
|---|---|---|
| Events sent | eventsSent / events_sent | Events you sent (POST /send-event) that produced at least one message for the application, counted on the day the event was sent. One event fanned out to three of the application's endpoints counts once. Replaying the event does not count it again. An event that matched no endpoint of the application is not counted. |
| Messages created | messagesCreated / messages_created | Messages created that day: one per event per subscribed endpoint, plus one for each manual replay or dead-letter retry (each one is a new message). |
| Delivery attempts | deliveryAttempts / delivery_attempts | HTTP requests made to the application's endpoints that day, including retries, whatever the outcome (success, failure or timeout). Attempts skipped because an endpoint's circuit breaker was open are not counted, since no request was sent. |
| Successful deliveries | successfulDeliveries / successful_deliveries | Attempts that day that the endpoint accepted (a 2xx response, or one of the endpoint's custom success codes). A message replayed and delivered again counts again. |
| Failed (DLQ) | failedMessages / failed_messages | Messages moved to the dead-letter queue that day after their retries ran out. A message that is retried out of the DLQ and fails again counts again, because the retry is a new message. Archiving or deleting a DLQ message does not remove it from the count. |
| Bytes delivered | bytesDelivered / bytes_delivered | Total request-body size in bytes of the successful deliveries that day (the payload after any transform). Headers are not included. |
Which number to bill on? It depends on what you sell. Events sent matches "per event your customer subscribes to". Successful deliveries matches "per webhook delivered", counting each endpoint separately. Bytes delivered suits bandwidth-based pricing. Delivery attempts includes retries, so it is better suited to reporting than to billing.
Freshness and final numbers
The rollup runs every 10 minutes. Each response includes:
meta.dataThrough: when the rollup last ran. Today's numbers are as of this time.meta.closedThrough: the last UTC day whose numbers are final. A day becomes final about an hour after it ends in UTC. To invoice a month, export it onceclosedThroughis on or after the month's last day.
Usage history starts when the usage rollup was launched. At launch it backfilled from the messages still in retention, so days that were already older than your plan's retention period at that point may be incomplete or missing.
API
GET /api/organizations/{orgId}/webhook-applications/usageWorks with a dashboard session or an API key (x-api-key: whr_...). You need read access to
webhook applications and outbound messages (webhookApplications:read and outboundMessages:read).
Every role has both by default. Available on all plans.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
from | YYYY-MM-DD | First day of to's month | Start date (UTC), inclusive |
to | YYYY-MM-DD | Today (UTC) | End date (UTC), inclusive. At most 13 months after from. For example, from=2025-10-01 allows to up to 2026-10-31. |
granularity | day | month | day | One row per application per UTC day or per UTC calendar month |
applicationId | string | all applications | Only this application |
format | json | csv | json | csv streams the whole range as a file download |
limit | integer 1-5000 | 500 | Rows per page (JSON only) |
cursor | string | none | pagination.nextCursor from the previous page (JSON only) |
Rows are ordered by application, then period. An application or period with no activity has no row.
A range that starts or ends partway through a month still returns that month, with periodStart
and periodEnd limited to the range.
Example: last month, per customer
curl -H "x-api-key: whr_your_api_key" \
"https://api.hookbase.app/api/organizations/{orgId}/webhook-applications/usage?from=2026-09-01&to=2026-09-30&granularity=month"{
"data": [
{
"applicationId": "8b0c2a4e-1f3d-4c55-9a77-2f0e6d1b9c10",
"applicationName": "Acme Corp",
"externalId": "cust_42",
"applicationDeleted": false,
"period": "2026-09",
"periodStart": "2026-09-01",
"periodEnd": "2026-09-30",
"eventsSent": 18240,
"messagesCreated": 36512,
"deliveryAttempts": 37104,
"successfulDeliveries": 36470,
"failedMessages": 12,
"bytesDelivered": 48211904
}
],
"totals": {
"eventsSent": 18240,
"messagesCreated": 36512,
"deliveryAttempts": 37104,
"successfulDeliveries": 36470,
"failedMessages": 12,
"bytesDelivered": 48211904
},
"meta": {
"from": "2026-09-01",
"to": "2026-09-30",
"granularity": "month",
"applicationId": null,
"timezone": "UTC",
"dataThrough": "2026-10-18T12:09:00.000Z",
"closedThrough": "2026-10-17"
},
"pagination": { "hasMore": false, "nextCursor": null }
}totals covers the whole range and filter, not only the current page. If an application has been
deleted, its usage is still reported: applicationDeleted is true and applicationName /
externalId are null.
CSV
curl -H "x-api-key: whr_your_api_key" -o usage.csv \
"https://api.hookbase.app/api/organizations/{orgId}/webhook-applications/usage?from=2026-09-01&to=2026-09-30&granularity=month&format=csv"period,period_start,period_end,application_id,application_name,external_id,application_deleted,events_sent,messages_created,delivery_attempts,successful_deliveries,failed_messages,bytes_delivered
2026-09,2026-09-01,2026-09-30,8b0c2a4e-1f3d-4c55-9a77-2f0e6d1b9c10,Acme Corp,cust_42,false,18240,36512,37104,36470,12,48211904The file is streamed, so large organizations can export a full year without paging. Fields follow
RFC 4180: values containing commas, quotes or line breaks are quoted. An application name or
external ID that starts with =, +, -, @ or a tab is prefixed with ', so a spreadsheet
does not run it as a formula.
Errors
| Status | When |
|---|---|
400 | Invalid date, to before from, a range longer than 13 months, unknown granularity/format, malformed applicationId, limit out of range, or an invalid cursor |
403 | Your role or API key lacks webhookApplications:read or outboundMessages:read |
Billing pass-through recipe
- Create one application per customer, with
externalIdset to the customer ID in your billing system. - After a month ends, wait until
meta.closedThroughis on or after the month's last day. - Export that month with
granularity=month, as JSON or CSV. - For each row, look up the customer by
externalIdand report the metric you price on (for examplesuccessfulDeliveries) to your billing provider as a usage record or invoice line.
Because a closed day's numbers never change, re-running the export for the same month always gives the same result, so the job is safe to retry.