Automation
Webhooks
Register an outbound webhook through the Apiable Platform API: the URL, the events to list, an optional signing secret and headers. Apiable posts a Standard Webhooks payload with signature headers. Check each delivery's type, because a webhook can receive event types it did not list.
A webhook tells your systems when something happens in Apiable. You register an endpoint and the events it should hear about, and Apiable sends a signed HTTP POST to that URL when an event fires. You manage webhooks through the Platform API; the dashboard has no webhook screen.
How do you configure a webhook?
Send a POST to /api/webhooks on your portal's Platform API endpoint, with the URL to call and the events to list. The signing secret and headers are optional. If you leave the secret out, Apiable generates one and returns it once.
/api/webhooks curl -X POST "https://your-portal.api.apiable.io/api/webhooks" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "X-API-Version: 2024-09-25" \
-d '{
"url": "https://hooks.example.com/apiable",
"events": ["SUBSCRIPTION_CREATED", "SUBSCRIPTION_CANCELLED"],
"headers": { "X-Hook-Key": "your-own-value" }
}'A webhook configuration has these fields:
| Field | Required | What it is |
|---|---|---|
url | Yes | The endpoint Apiable posts to. Any URL that accepts an HTTP POST. |
events | Yes | The event types this webhook is for. See which events exist. |
whsec | No | The signing secret: base64, prefixed whsec_, decoding to 24 to 64 bytes. Apiable generates one if you leave it out. |
headers | No | Extra HTTP headers Apiable sends with every delivery, for example a value your endpoint checks. |
authorization | No | Deprecated. Event deliveries do not send this value as you entered it. Put an Authorization header in headers instead. |
How do you change or remove a webhook?
Update a webhook with PUT /api/webhooks/{id}, sending the whole configuration: url, events, and any headers. Leave whsec out to keep the current secret. Remove a webhook with DELETE /api/webhooks/{id}, which returns 204.
| Call | What it does |
|---|---|
GET /api/webhooks | Lists your webhooks, with secrets masked. |
GET /api/webhooks/{id} | Reads one webhook, with its secret masked. |
PUT /api/webhooks/{id} | Replaces the webhook's configuration. A field you leave out, such as headers, is removed. |
DELETE /api/webhooks/{id} | Removes the webhook. |
What events can a webhook receive?
Nine event types, covering subscriptions, invoices and scope grants. You list the ones a webhook is for in events. A webhook can still receive other types, so check each delivery's type.
| Event type | Sent when |
|---|---|
SUBSCRIPTION_CREATED | A subscription is created. |
SUBSCRIPTION_CANCELLED | A subscription is cancelled. |
SUBSCRIPTION_CHANGED | A subscription changes and ends up pending cancellation, pending payment, payment failed, rejected or expired. Approving a subscription, and other changes to an active one, send no event. |
SUBSCRIPTION_AUTH_CHANGED | A subscription's credentials are created, changed or regenerated. |
INVOICE_ATTENTION_REQUIRED | A daily check finds paid invoices from the previous day that Apiable cannot match to a subscription or user. |
SCOPE_GRANT_REQUESTED | A consumer requests a scope grant. |
SCOPE_GRANT_APPROVED | A scope grant request is approved. |
SCOPE_GRANT_DECLINED | A scope grant request is declined. |
SCOPE_GRANT_REVOKED | A scope grant is revoked. |
What does a webhook delivery look like?
A JSON POST in the Standard Webhooks shape: an event id, the event type, a timestamp, a data object, and an optional source URL. Do not rely on the contents of data, which can arrive empty.
{
"id": "67040bcb1eb964694d999a68",
"type": "SUBSCRIPTION_CREATED",
"data": {},
"timestamp": "2026-09-26T10:15:30.123Z",
"source": "https://your-portal.apiable.io/api/subscriptions/664f1c2a9b1e4a0012ab34cd"
}| Field | What it carries |
|---|---|
id | The event id. It is the same as the webhook-id header and stays the same when Apiable retries, so you can use it to ignore duplicates. |
type | One of the event types above, or TEST_EVENT for a test delivery. |
timestamp | When Apiable sent this attempt, in ISO 8601. |
data | An object for event details. It can be empty, so do not build on its contents. |
source | A URL on your portal related to the event. |
Use a delivery as a signal. When one arrives, read the current state through the Platform API, for example GET /api/subscriptions?sort=updated:desc for the most recently changed subscriptions.
How does Apiable sign webhook payloads?
Apiable signs each delivery with the Standard Webhooks scheme. It sends three headers, and the signature is an HMAC-SHA256 over the message, made with your webhook's signing secret.
| Header | What it carries |
|---|---|
webhook-id | The event id, the same on every retry. |
webhook-timestamp | The send time as integer Unix seconds. |
webhook-signature | A space-separated list of versioned signatures, each v1,<base64>. |
To verify a delivery:
- Read
webhook-id,webhook-timestampandwebhook-signaturefrom the request headers. - Build the signed content as
{webhook-id}.{webhook-timestamp}.{body}, joined with full stops, where the body is the raw request body as received. - Take your secret, drop the
whsec_prefix, and base64-decode the rest to get the key. - Compute an HMAC-SHA256 over the signed content with that key, and base64-encode the result.
- Compare it, in constant time, with each
v1entry inwebhook-signature. Accept the request if one matches. - Reject the request if
webhook-timestampis too far from the current time, which guards against replays. The Standard Webhooks reference libraries allow five minutes either way.
How does Apiable retry a failed delivery?
Apiable treats 200, 201, 202 and 204 as success. Any other status, a timeout or an unreachable endpoint is a failure. Apiable tries once when the event happens, then again at the top of each hour, up to three attempts in total, and then stops.
Return one of those four statuses as soon as you have accepted the event, and do slow work afterwards. Other success codes, such as 206, count as failures. Because retries carry the same webhook-id, keep track of the ids you have processed.
How do you test a webhook and see its deliveries?
Send a test with GET /api/webhooks/{id}/test. Apiable posts a signed TEST_EVENT to the webhook's URL and returns the status and body it got back. Read recent deliveries with GET /api/webhooks/{id}/history, which returns the response lines Apiable recorded over the last 24 hours.
/api/webhooks/{id}/test /api/webhooks/{id}/history The test response reads like Test event sent to: <url> with response.status: 200 OK and response.body: .... Each history line starts with the attempt time and names the event id, the status your endpoint returned and its response body, or the connection error.
Troubleshooting
Match what you see to the fix.
| What you see | What to do |
|---|---|
"Invalid whsec, it must be base64 encoded and prefixed with whsec_" | Send a base64 secret with the whsec_ prefix, or leave whsec out so Apiable generates one. |
| "Invalid whsec: Decoded value must be Between 24 bytes (192 bits) and 64 bytes (512 bits)" | Use a secret that decodes to 24 to 64 bytes. |
404 "Webhook configurations not found" when you list webhooks | You have no webhooks yet. Create one first. |
| Your endpoint receives event types you did not list | Expected. Check type, ignore the types you do not handle, and return 200 for them. |
| A delivery keeps retrying | Your endpoint did not return 200, 201, 202 or 204. Check the history for the status Apiable received. |
| A delivery stopped arriving after a few hours | Apiable stops after three attempts. Fix the endpoint; later events deliver normally. |
Your endpoint rejects the Authorization value | The deprecated authorization field is not sent as entered. Move the header into headers. |
| Verification fails with a timestamp error | Check your server clock. webhook-timestamp is in seconds. |
| The test call reports a connection error | The URL is wrong, or your endpoint is not reachable from the internet. |