Webhooks
Webhooks deliver event notifications from Floatless to your application when billing state changes.
Webhook documentation belongs in the website docs. Webhook endpoints and signing secrets can be managed in the product console or through the Public API.
Endpoint management API
| Method | Endpoint | Description |
|---|---|---|
| POST | /webhook-endpoints |
Create a webhook endpoint |
| GET | /webhook-endpoints |
List webhook endpoints |
| GET | /webhook-endpoints/{endpoint_id} |
Retrieve a webhook endpoint |
| PATCH | /webhook-endpoints/{endpoint_id} |
Update URL, event subscriptions, status, or description |
| DELETE | /webhook-endpoints/{endpoint_id} |
Delete a webhook endpoint |
Delivery history API
| Method | Endpoint | Description |
|---|---|---|
| GET | /webhook-events |
List webhook delivery events |
| GET | /webhook-events/{event_id} |
Retrieve one webhook delivery event |
| POST | /webhook-events/{event_id}/retry |
Retry a failed or retrying webhook event |
Create a webhook endpoint
curl https://api.floatless.com/api/public/v1/webhook-endpoints \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/floatless",
"events": ["invoice.paid", "subscription.*"],
"description": "Production billing events"
}'
The creation response includes the signing secret once:
{
"data": {
"id": "0d75f867-0f90-4d32-aeb6-2df8136bdfc2",
"object": "webhook_endpoint",
"url": "https://example.com/webhooks/floatless",
"events": ["invoice.paid", "subscription.*"],
"status": "ACTIVE",
"description": "Production billing events",
"created_at": "2026-07-07T10:00:00Z",
"updated_at": "2026-07-07T10:00:00Z",
"secret": "whsec_..."
}
}
Store the secret securely. It is not returned by list, retrieve, or update responses.
URL requirements
Production webhook URLs must use HTTPS.
Local development URLs using http://localhost or http://127.0.0.1 are allowed for testing.
Event subscriptions
You can subscribe to specific events or family wildcards.
| Event | Description | Currently emitted |
|---|---|---|
invoice.posted |
Invoice issued | Yes |
invoice.paid |
Invoice paid | Yes |
invoice.created |
Invoice created | Reserved |
invoice.overdue |
Invoice moved overdue | Reserved |
invoice.payment_failed |
Invoice payment failed | Reserved |
subscription.created |
Subscription created | Yes |
subscription.canceled |
Subscription canceled | Yes |
subscription.updated |
Subscription changed | Reserved |
subscription.activated |
Subscription activated | Reserved |
order.created |
Order created | Reserved |
order.completed |
Order completed | Reserved |
payment.succeeded |
Payment succeeded | Yes |
payment.failed |
Payment failed | Reserved |
customer.created |
Customer created | Reserved |
customer.updated |
Customer updated | Reserved |
Supported wildcards:
invoice.*subscription.*order.*payment.*customer.*
List webhook events
curl "https://api.floatless.com/api/public/v1/webhook-events?status=FAILED&limit=25" \
-H "Authorization: Bearer sk_live_..."
Query parameters
| Parameter | Type | Description |
|---|---|---|
endpoint_id |
UUID | Filter by endpoint |
status |
string | PENDING, SUCCESS, FAILED, or RETRYING |
event_type |
string | Filter by event type |
limit |
integer | Number of results, 1 to 100 |
offset |
integer | Pagination offset |
Retry a webhook event
curl https://api.floatless.com/api/public/v1/webhook-events/64d63c4a-3817-4d5d-bd38-bad334be58aa/retry \
-H "Authorization: Bearer sk_live_..." \
-X POST
Successful events cannot be retried.
Retry policy
Delivery is at-least-once with automatic retries:
| Setting | Value |
|---|---|
| Attempts | Up to 5 per event |
| Backoff | 1s, 2s, 4s, 8s, 16s between attempts |
| Timeout | 10 seconds per attempt |
| Failure recording | Timeout recorded as response code 408; connection error as 0 |
| Response body | Last 1000 characters retained |
An event in RETRYING can also be retried manually from the delivery history API or the console's Developer page.
Payload shape
{
"id": "5b4c33b4-6a86-4e5d-9fd9-2d3a7f09d874",
"type": "invoice.paid",
"created": 1783400400,
"data": {
"id": "5b4c33b4-6a86-4e5d-9fd9-2d3a7f09d874",
"object": "invoice",
"customer_id": 123,
"status": "PAID"
}
}
created is a Unix timestamp (integer seconds). id is the event's delivery ID — store it for duplicate suppression.
Delivery headers
Webhook deliveries include:
| Header | Description |
|---|---|
X-Floatless-Signature |
HMAC SHA-256 signature |
X-Floatless-Event |
Event type |
X-Floatless-Delivery |
Delivery event ID |
User-Agent |
Floatless-Webhooks/1.0 |
Endpoint requirements
- Use HTTPS in production.
- Return a 2xx response after receiving and validating the event.
- Process expensive work asynchronously.
- Store processed event IDs to handle duplicate delivery.
- Treat webhook delivery as at-least-once.
Signature verification
Every endpoint has a signing secret. Use it to verify incoming webhook requests before trusting the payload.
The signature value is generated with HMAC SHA-256 over the exact raw JSON body (compact serialization) and delivered in the X-Floatless-Signature header as sha256=<hex_digest>. Verify by computing HMAC over the raw request body bytes (not re-serialized JSON) and comparing with a constant-time comparison:
import crypto from "node:crypto";
export function verifyWebhook(rawBody, secret, signatureHeader) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
try {
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader),
);
} catch {
return false;
}
}