Developer Webhook API and Delivery
Register ISPbills webhooks, verify HMAC signatures, and handle delivery retries safely
Register ISPbills webhooks, verify HMAC signatures, and handle delivery retries safely
On this page
Webhooks let ISPbills send an event to your server instead of requiring your integration to poll the API. Webhook endpoint management is available through API v2 and the API Management screen.
For the operator-facing screen and a non-code overview, see Admin Webhook Setup.
Register an endpoint
Register a public URL with at least one event. The request field is events_json:
POST /api/v2/webhooks
Authorization: Bearer YOUR_V2_TOKEN
Accept: application/json
Content-Type: application/json
{
"url": "https://integration.example.com/webhooks/ispbills",
"events_json": ["customer.created", "payment.received"],
"is_active": true
}
The create response includes a signing secret:
{
"data": {
"id": 17,
"url": "https://integration.example.com/webhooks/ispbills",
"events": ["customer.created", "payment.received"],
"is_active": true,
"created_at": "2026-08-16T12:00:00.000000Z"
},
"secret": "ONE_TIME_SIGNING_SECRET"
}
Store the secret immediately in your server-side secret manager. It is returned when the endpoint is created and is not included when endpoints are listed.
Management endpoints
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v2/webhooks |
List endpoint IDs, URLs, event subscriptions, active state, and creation time |
POST |
/api/v2/webhooks |
Register an endpoint |
PUT |
/api/v2/webhooks/{id} |
Update url, events_json, or is_active |
DELETE |
/api/v2/webhooks/{id} |
Delete an endpoint |
All records are restricted to the authenticated API client’s operator.
Event names
The API Management screen currently offers these subscriptions:
| Event | Meaning |
|---|---|
customer.created |
Customer created |
customer.updated |
Customer details updated |
customer.deleted |
Customer deleted |
customer.suspended |
Customer suspended |
customer.renewed |
Customer renewed |
bill.created |
Bill created |
invoice.generated |
Invoice generated |
payment.received |
Payment recorded or confirmed |
payment.failed |
Payment attempt failed |
subscription.expired |
Subscription expired |
* |
Subscribe to every dispatched event |
Your receiver should safely ignore unknown event names. New event types can be introduced without changing the payload envelope.
Delivery request
ISPbills sends an HTTP POST with these headers:
Content-Type: application/json
X-Webhook-Event: customer.created
X-Webhook-Signature: sha256=HEX_HMAC_DIGEST
Payload envelope:
{
"event": "customer.created",
"timestamp": "2026-08-16T12:00:00.000000Z",
"data": {
"id": 1234,
"name": "Example Customer",
"status": "active"
}
}
The contents of data depend on the event. Use event to choose a handler and tolerate additional fields.
Verify the signature
The signature is:
sha256=HMAC_SHA256(raw_request_body, webhook_secret)
Verify the exact raw bytes before decoding JSON. Do not parse and re-encode the body before calculating the HMAC.
PHP example:
$rawBody = file_get_contents('php://input');
$received = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $webhookSecret);
if (! hash_equals($expected, $received)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($rawBody, true, flags: JSON_THROW_ON_ERROR);
Node.js example:
import crypto from 'node:crypto';
const expected = `sha256=${crypto
.createHmac('sha256', process.env.ISPBILLS_WEBHOOK_SECRET)
.update(rawBody)
.digest('hex')}`;
const valid = crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(request.headers['x-webhook-signature'] || ''),
);
Check buffer lengths before timingSafeEqual in production because it throws when lengths differ.
Delivery and retries
Webhook delivery runs asynchronously. A failed delivery is attempted up to three times with increasing delays. The receiving endpoint should:
- Verify the signature.
- Store the event or enqueue local work.
- Return a
2xxresponse promptly. - Process slow operations outside the HTTP request.
Delivery is at least once. A retry can deliver the same event more than once, so make handlers idempotent. A practical deduplication key combines the event name, relevant resource ID, and timestamp until a dedicated delivery ID is available.
Operational checklist
- Use an HTTPS endpoint with a valid certificate.
- Keep the signing secret outside application logs and source code.
- Compare signatures with a timing-safe function.
- Allow new JSON fields and unknown event names.
- Monitor non-
2xxresponses and receiver latency. - Disable or delete an endpoint before retiring its receiver.
- Rotate a compromised secret by replacing the webhook endpoint.