Webhooks
Driptab fires HTTP webhooks when billing events occur. Your system listens on an endpoint, verifies the signature, and acts on the events that matter to you.
Registering an endpoint
dtab webhooks create \
--url https://app.example.com/hooks/driptab \
--events invoice.created,payment.succeeded,subscription.suspend_requested
Or via the API:
POST /v1/webhooks
X-Project-Id: <project_id>
Authorization: Bearer dtab_…
{
"url": "https://app.example.com/hooks/driptab",
"events": ["invoice.created", "payment.succeeded", "subscription.suspend_requested"]
}
Response includes the endpoint's secret — a per-endpoint HMAC-SHA256 signing key
with the prefix whsec_. Store this secret; it is shown once. Subsequent list
responses mask it.
You can register multiple endpoints, each subscribing to different event sets.
Webhook payload format
All webhook deliveries share the same envelope:
{
"event": "invoice.created",
"created_at": "2026-06-08T02:57:35.000Z",
"data": {
"invoice": { ... },
"customer": { ... }
}
}
The data object contains the relevant billing objects. See the
webhook events reference for the full payload shape
of each event type.
Verifying the signature
Every delivery includes an X-Driptab-Signature header containing an HMAC-SHA256 hex
digest of the raw request body, signed with the endpoint's secret.
Verification in Node.js / TypeScript:
import { createHmac } from 'crypto';
function verifyDriptabWebhook(secret: string, body: string, sig: string): boolean {
const expected = createHmac('sha256', secret).update(body).digest('hex');
return expected === sig;
}
// In your request handler:
const sig = req.headers['x-driptab-signature'];
const body = req.rawBody; // unparsed string
const valid = verifyDriptabWebhook(process.env.DRIPTAB_WEBHOOK_SECRET, body, sig);
if (!valid) return res.status(401).send('Invalid signature');
Verification in Python:
import hmac, hashlib
def verify(secret: str, body: bytes, sig: str) -> bool:
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
Always verify the signature before processing the payload. Use the raw (pre-parsed)
request body — not JSON.stringify(req.body), which may differ from the original.
Responding to deliveries
Your endpoint must return a 2xx response within 10 seconds. Driptab records the
delivery result:
delivered— your endpoint returned a 2xx.failed— your endpoint returned non-2xx, timed out, or was unreachable.
Failed deliveries are retried with exponential backoff. The next_retry_at field on
the delivery record indicates the next attempt.
Acknowledge the webhook quickly — do expensive processing in the background. Return
200 OK immediately if you need more than a second to process.
Available events
| Event | Fired when |
|---|---|
invoice.created |
An invoice is generated (period close or immediate charge) |
invoice.payment_overdue_reminder_1 |
Invoice is 3 days past due (configurable via driptab.yaml) |
invoice.payment_overdue_reminder_2 |
Invoice is 7 days past due |
subscription.period_renewed |
A billing period closes and a new one starts |
subscription.suspend_requested |
Dunning step requesting suspension (14 days past due) |
subscription.terminate_requested |
Dunning step requesting termination (30 days past due) |
addon.applied |
An add-on is applied to a customer |
payment.succeeded |
A payment is recorded against an invoice |
See the webhook events reference for full payload shapes.
The dunning pattern
Driptab does not suspend or terminate subscriptions automatically. It fires webhook events at configurable milestones; your system acts on them.
# driptab.yaml
dunning:
enabled: true
steps:
- days_after_due: 3
webhook_event: invoice.payment_overdue_reminder_1
- days_after_due: 7
webhook_event: invoice.payment_overdue_reminder_2
- days_after_due: 14
webhook_event: subscription.suspend_requested
- days_after_due: 30
webhook_event: subscription.terminate_requested
Your webhook handler decides what to do on each step:
- Send a reminder email (days 3 and 7).
- Call
POST /v1/subscriptions/:id/suspend(day 14) to stop service. - Call
POST /v1/subscriptions/:id/terminate(day 30) to permanently close.
This design gives you full control over customer communication and grace periods without requiring those decisions to live inside Driptab.
Delivery log
Every dispatch attempt is recorded in webhook_deliveries. View the log:
dtab webhooks deliveries --endpoint <endpoint_id>
Or via the API:
GET /v1/webhook-deliveries?endpoint_id=<id>