With the M8com Billing Data webhook it is possible for a wholesale partner to automatically receive billing records for all their customer organizations. M8com posts individual billing records (one per product per organization) to the partner's configured webhook endpoint at the end of each billing period.
There are two main features:
- Billing data posting (monthly or quarterly)
- Refund posting (optional)
When the webhook is configured and activated for a wholesale partner, M8com will automatically post billing data when:
- a billing period ends and the billing sync is triggered
- a manual billing data post is initiated by an administrator
- a retry is triggered for previously failed records
If the refund feature is enabled, M8com will post refund requests to a separate refund URL when:
- a refund is initiated for a previously billed record
How it works
Overview - Billing Data Posting
When a billing period ends (monthly or quarterly), the billing sync process triggers the billing data webhook for each configured wholesale partner. M8com collects billing report data for all customer organizations under the partner, calculates amounts from the partner's pricebook (including 25% Swedish VAT), and posts one HTTP request per product per organization to the partner's webhook endpoint.
Flow of events:
- Billing sync triggers
postPartnerBillingDatafor the partner
- M8com retrieves the partner's active billing webhook configuration
- M8com calculates the billing period dates (the previous month or quarter)
- For each customer organization, M8com fetches the billing report
- For each product with quantity > 0, a billing record is constructed
- Records are validated (customer billing ID must exist) and sent in batches of 50
- Each request/response is logged in
billingWebhookLogfor auditing and retry support
Overview - Retry Logic
If any records fail during a billing run (e.g. due to missing billing IDs or network errors), M8com stores them and will automatically retry failed records on the next run for the same period. Records that succeeded are not re-sent. New organizations added since the previous run are also included.
Webhook Setup for partners
The billing data webhook is configured per wholesale partner in the partner administration settings. The setup requires the manageBilling permission and is handled by Partner administration.
Connecting a new webhook
When connecting a new billing data webhook, the following must be configured:
| Setting | Required | Description |
|---|---|---|
| Webhook URL | Yes | The HTTPS endpoint where billing data will be posted |
| Authentication Method | Yes | Either OAuth2 or Custom Auth |
| Partner Customer Billing ID Field | Yes | The JSON field name used for the customer billing ID in each request (e.g. customerId) |
| Billing Data Period | Yes | Monthly or Quarterly |
| Include Partner Organization | No | Whether to include the partner's own organization in billing |
| Partner Organization Billing ID | Conditional | Required if "Include Partner Organization" is enabled |
Authentication Methods
OAuth2 supports two grant types:
| Setting | Required | Description |
|---|---|---|
| Grant Type | Yes | Client Credentials or Authorization Code |
| Client ID | Yes | OAuth2 client identifier |
| Client Secret | Yes | OAuth2 client secret |
| Token URL | Yes | OAuth2 token endpoint URL |
| Authorization Base URL | Conditional | Required for Authorization Code grant type |
| Scope | No | OAuth2 scope (Authorization Code only) |
| Refresh Token Timeout | No | How long before the refresh token expires (Authorization Code only) |
| Authorization Parameters | No | Additional parameters for the authorization request |
Custom Auth allows authentication via custom parameters in:
| Location | Description |
|---|---|
| Headers | Custom key-value pairs sent as HTTP headers |
| Body | Custom key-value pairs merged into the request body |
| Query String | Custom key-value pairs appended to the URL |
At least one custom auth parameter (in any location) must be configured.
Additional Configuration
| Setting | Required | Description |
|---|---|---|
| Additional Static Parameters | No | Extra key-value pairs included in every request body (e.g. partner-specific identifiers) |
| Refund Enabled | No | Enable refund posting to a separate endpoint |
| Refund URL | Conditional | Required if refunds are enabled |
| Refund Reference ID Field | Conditional | Field name in the billing response to use as reference ID for refunds |
| Refund Static Parameters | No | Extra key-value pairs included in every refund request |
Implementation Notes
This specification is for the implementer of a billing data receiver, and describes the request format that must be supported by the receiving endpoint.
Notes
- It's the responsibility of the webhook receiver to be available and handle requests correctly.
- M8com sends billing data as individual HTTP POST requests - one request per product per organization.
- Requests are sent in batches of up to 50 concurrent requests.
- Each request has a 20 second timeout.
- All requests use
Content-Type: application/jsonandAccept: application/json.
- Authentication (OAuth2 token or custom auth) is handled automatically by M8com before each batch.
Adding is not breaking
- New fields may be introduced to the JSON request body over time and is not considered a breaking change. Integrators must ensure that their integrations ignore unknown fields by default.
Billing Data Posting
REQUEST
M8com will send an HTTP POST request to the configured webhook URL for each billable product per organization. The request body is in Content-Type: application/json format.
Request body
| Field | Type | Optional | Description |
|---|---|---|---|
[partnerCustomerBillingIdField] | string | No | Dynamic field name • the field name is configured per webhook (e.g. customerId). The value is the organization's partner customer billing ID. |
product | string | No | The product type identifier (e.g. pbx-unlimited, sms-package, phone-number). |
amount | number | No | Price in the smallest currency unit (ore) including 25% VAT. For example, 15000 means 150.00 SEK. |
vat | number | No | VAT percentage expressed as an integer. Always 2500 (representing 25.00%). |
currency | string | No | Currency code. Always SEK. |
clientTransactionId | string | No | A unique UUID v4 identifier for this specific billing record. Can be used for idempotency checks. |
invoiceText | string | No | Human-readable name for the product, suitable for display on an invoice. Falls back to the product type if no display name is configured. |
quantity | number | No | The number of licenses. For package-type products, this is the end-of-period quantity. For all other products, this is the peak quantity during the period. |
articleId | string | No | Article identifier, same as the product field. |
| additional static params | varies | Yes | Any additional key-value pairs configured in the webhook's "Additional Static Parameters" are spread into the request body at the top level. |
Amount Calculation
The amount field is calculated as:
amount = floor(recurringPrice * 100 * 1.25)Where:
recurringPriceis the price from the partner's pricebook (in SEK)
* 100converts to ore (smallest currency unit)
* 1.25adds 25% Swedish VAT
floor()rounds down to the nearest integer
Quantity Selection
| Product Type | Quantity Used |
|---|---|
Package products (e.g. pbx-package, package-unlimited) | periodEndQuantity • the quantity at the end of the billing period |
| All other products | periodPeakQuantity • the highest quantity observed during the billing period |
Example Request
POST https://partner-billing-system.example.com/api/billing
Content-Type: application/json
Authorization: Bearer <oauth2-access-token>
{
"customerId": "ORG-12345",
"product": "pbx-unlimited",
"amount": 49625,
"vat": 2500,
"currency": "SEK",
"clientTransactionId": "550e8400-e29b-41d4-a716-446655440000",
"invoiceText": "PBX Unlimited",
"quantity": 5,
"articleId": "pbx-unlimited"
}In this example:
customerIdis the dynamic field name configured as thepartnerCustomerBillingIdField
amountof49625ore = 496.25 SEK (including 25% VAT)
quantityof5means 5 licenses of this product
Example with Additional Static Parameters
If the webhook is configured with additional static parameters like { "partnerId": "ACME-001", "region": "SE" }:
{
"partnerId": "ACME-001",
"region": "SE",
"customerId": "ORG-12345",
"product": "pbx-unlimited",
"amount": 49625,
"vat": 2500,
"currency": "SEK",
"clientTransactionId": "550e8400-e29b-41d4-a716-446655440000",
"invoiceText": "PBX Unlimited",
"quantity": 5,
"articleId": "pbx-unlimited"
}RESPONSE
M8com expects an HTTP 200 response. The response body is logged for auditing purposes. M8com analyzes the response body for error indicators even on HTTP 200 responses:
| Error Indicator | Description |
|---|---|
statusIndicator not equal to "0" | Error indicator |
error: true | Generic error flag |
success: false | Generic success flag |
status: "error" or status: "failed" | Status field error values |
errorCode or error_code present | Error code fields |
If any of these indicators are found, the record is marked as failed and can be retried.
Billing Period Calculation
The billingMonth parameter uses the format YYYY-MM and represents the month after the billing period has ended.
Monthly
| billingMonth | Period Start | Period End |
|---|---|---|
2025-02 | 2025-01-01 | 2025-01-31 |
2025-03 | 2025-02-01 | 2025-02-28 |
2025-07 | 2025-06-01 | 2025-06-30 |
Quarterly
For quarterly billing, billingMonth must be the month after a quarter ends (01, 04, 07, or 10).
| billingMonth | Period Start | Period End | Quarter |
|---|---|---|---|
2025-01 | 2024-10-01 | 2024-12-31 | Q4 2024 |
2025-04 | 2025-01-01 | 2025-03-31 | Q1 2025 |
2025-07 | 2025-04-01 | 2025-06-30 | Q2 2025 |
2025-10 | 2025-07-01 | 2025-09-30 | Q3 2025 |
Refund Posting
Overview
When refund posting is enabled, M8com can send refund requests to a separate configured URL. Refunds reference a previous billing record using a field from the original billing response.
Refund Configuration
| Setting | Description |
|---|---|
| Refund URL | The HTTPS endpoint where refund requests are posted |
| Refund Reference ID Field | The field name in the original billing response's responseData to use as the reference ID |
| Refund Static Parameters | Additional key-value pairs included in every refund request body |
Details & limitations
- All amounts are in SEK (Swedish Krona) and expressed in ore (1/100 SEK).
- VAT is always 25% (Swedish VAT) and is included in the
amountfield.
- A webhook request times out after 20 seconds.
- Records are sent in batches of 50.
- Each billing record gets a unique
clientTransactionId(UUID v4) for idempotency.
- Products with zero quantity are not billed.
- Products included via another product (e.g. included in a package) are excluded to avoid double-billing.
- The
partnerCustomerBillingIdFieldis a dynamic field name - the actual JSON key depends on the webhook configuration.
- If a customer organization is missing a
partnerCustomerBillingId, the record is logged as failed and can be retried after the ID is configured.
- For the partner's own organization, the billing ID comes from the webhook configuration (
partnerOrganizationBillingId) rather than the organization record.
- All requests and responses are logged in
billingWebhookLogfor auditing, debugging, and retry support.
- If all records for a run succeed, the run is marked as completed and will not be re-sent.
- If a run has already been completed for a given period, subsequent triggers return immediately without re-sending.