Guide to Stigg Webhooks: Features and Best Practices
Stigg webhooks tell your systems when something changes in your pricing and entitlements setup: a customer signs up, a subscription is created, upgraded or canceled, a trial converts, a customer's entitlements change, usage crosses a limit, or a credit balance runs low. If you use Stigg to run plans and feature access, webhooks are how those changes reach your CRM, billing, analytics and provisioning code.
This guide covers how Stigg webhooks are set up and authenticated, the events and the shape of their payloads, the gaps in Stigg's documentation, and the best practices for running a Stigg receiver in production.
What are Stigg webhooks?
A Stigg webhook is an endpoint URL plus a list of events, configured in the Stigg app. When one of those events happens, Stigg sends a JSON POST to the URL. Webhooks are active as soon as you create them.
Stigg doesn't sign its webhooks. Each webhook has a secret, and Stigg sends that secret unchanged in a Stigg-Webhooks-Secret header on every request. Your receiver authenticates a request by checking that the header matches the secret on the webhook's details page. Stigg says HMAC signature support is on its roadmap.
The body is flat. The event name is in type, and the changed entity's fields sit beside it at the top level, along with a messageId for de-duplication on most events.
Stigg webhook features
| Feature | Details |
|---|---|
| Configuration | Settings > Integrations > Webhooks in the Stigg app |
| Fields | Service name, endpoint URL, events to send |
| Method and format | POST, application/json |
| Authentication | The webhook secret, sent verbatim in Stigg-Webhooks-Secret |
| Signature | None. HMAC signing is on Stigg's roadmap |
| Event name | type in the body |
| De-duplication key | messageId in the body |
| Response window | A 2xx within 30 seconds |
| Retries | 3 immediate retries, then 3 retries 30 seconds apart |
| After retries | The event is marked failed, but Stigg stores it and can resend it on request |
| Ordering | Not guaranteed |
| Testing | Send test event, which shows the request body and your endpoint's response |
| Source IP | 18.119.35.43 (Stigg says the list may change and it will notify you) |
Stigg also offers durable queues (Amazon SQS) as an alternative way to receive the same kinds of events, if you'd rather not run an HTTP endpoint.
Common events
Stigg publishes its webhook events as an OpenAPI document with a schema and example for each one. The main groups are:
| Area | Events |
|---|---|
| Customers | customer.created, customer.updated, customer.deleted, customer.payment_failed |
| Subscriptions | subscription.created, subscription.updated, subscription.canceled, subscription.expired, subscription.create_, subscription.billing_, subscription.spend_ |
| Trials | subscription.trial_, subscription.trial.ends_ (7 days before expiry), subscription.trial_, subscription.trial_ |
| Entitlements | entitlements.updated, entitlement.usage_, promotionalEntitlement.granted, .updated, .revoked, .ends_soon, .expired |
| Credits | credit_grant.created, .updated, .balance.low, .depleted, .expired, credit_balance.low, credit_balance.depleted, automatic_, automatic_, credits.automatic_ |
| Catalog | product.*, plan.*, addon.* and feature.* (created, updated, deleted), feature_group.* (created, updated, archived, un_archived), coupon.* (created, updated, archived) |
| Integrations | sync.failed, data_export.sync_failed |
The naming is not consistent, so copy names exactly: subscription.trial_ but subscription.trial.ends_, and camelCase promotionalEntitlement.* beside snake_case everywhere else.
Here is Stigg's entitlements.updated example, trimmed:
{
"messageId": "ce2605be-8eed-41cf-912c-def052682dbe",
"traceId": "49146c12-8af0-44ff-a666-1f46255aecd8",
"timestamp": "2025-11-13T21:41:56.923Z",
"type": "entitlements.updated",
"trigger": "subscription_updated",
"customer": {
"id": "f29e950f-99cb-4f06-8395-434fb5259a58",
"name": "Ryan Bukoski's Organization",
"email": "test@example.com",
"billingId": "cus_TPyDVBZKeaQgnx"
},
"resource": {
"id": "device-34ea9346-4a37-4c5d-82b1-0e42c45dcd77",
"entityId": "14edaa7c-5326-41a1-99bb-b6365f0a0822"
},
"entitlements": [
{
"feature": {
"id": "feature-1-tb-cloud-storage",
"name": "1 TB cloud storage",
"featureType": "NUMBER",
"meterType": "INCREMENTAL",
"unit": "Tbs"
}
}
]
}
Other events carry the entity itself at the top level. subscription.trial_, for example, has the subscription's id, entityId, status, billing period dates, isTrialSubscription and a reason such as TRIAL_ENDED next to type. Entities carry both an id and an entityId. In Stigg's examples, id is a readable identifier and entityId is a UUID, so pick one and use it consistently as your key.
Setting up Stigg webhooks
- In the Stigg app, go to Settings > Integrations > Webhooks and click + New webhook.
- Enter a service name, your endpoint URL, and the events to send, then click Add. The webhook starts sending immediately.
- Open the webhook's details page, copy its secret, and store it server-side, for example as
STIGG_WEBHOOK_SECRET. - Use Send test event on one of the webhook's events to check that your endpoint answers with a
2xx.
If you use Hookdeck, create a Stigg source, paste the webhook secret into it, and use the Hookdeck source URL as the endpoint URL in Stigg. Hookdeck compares the Stigg-Webhooks-Secret header with that value. It accepts every request with a 200 first and checks the header afterwards, so a request with a missing or wrong secret is recorded as failed verification and is not delivered to your service, but Stigg still sees a success.
Securing Stigg webhooks
There is nothing to compute. Read the Stigg-Webhooks-Secret header and compare it with your stored secret in constant time. Because the body isn't signed, you can parse it however you like.
const crypto = require('crypto');
const express = require('express');
function verifyStiggSecret(headerValue, expectedSecret) {
if (!headerValue || !expectedSecret) return false;
const received = Buffer.from(headerValue, 'utf8');
const expected = Buffer.from(expectedSecret, 'utf8');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
const app = express();
app.post('/webhooks/stigg', express.raw({ type: '*/*' }), (req, res) => {
const secret = process.env.STIGG_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500); // fail closed
if (!verifyStiggSecret(req.get('stigg-webhooks-secret'), secret)) {
return res.sendStatus(401);
}
let event;
try {
event = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
// Route on event.type, deduplicate on event.messageId, then process asynchronously.
res.sendStatus(200);
});
The same check in Python:
import hmac
def verify_stigg_secret(header_value, expected_secret: str) -> bool:
if not header_value or not expected_secret:
return False
return hmac.compare_digest(header_value.encode("utf-8"), expected_secret.encode("utf-8"))
# In FastAPI: verify_stigg_secret(request.headers.get("stigg-webhooks-secret"),
# os.environ["STIGG_WEBHOOK_SECRET"])
The secret travels with every request, so anyone who captures one request can send forged ones. Serve the endpoint over HTTPS only. Stigg publishes a source IP, 18.119.35.43, which you can allowlist as a second check, keeping in mind that Stigg says the list may change.
Make Stigg webhooks production-ready. Hookdeck Event Gateway checks the
Stigg-Webhooks-Secretheader, accepts every subscription and entitlement event as soon as it arrives, and durably queues it, so a slow deploy doesn't use up Stigg's short retry schedule.
Stigg webhook limitations and pain points
A shared secret instead of a signature
The Problem: Nothing proves that the body you received is the body Stigg sent.
Why It Happens: Stigg sends the secret itself in a header and signs nothing. A captured request can be replayed or altered and its header still matches.
Workarounds:
- Use HTTPS, and allowlist Stigg's published IP address.
- For actions that grant or remove access, treat the webhook as a notification and read the current state from the Stigg API before acting.
- Watch Stigg's changelog for HMAC signing, which it lists as on its roadmap.
How Hookdeck Can Help: Hookdeck checks the header for you, so the secret lives on the Hookdeck source and not in every service, and requests with the wrong secret are recorded as failed verification instead of being delivered.
A short retry schedule
The Problem: An outage of a couple of minutes is enough to lose events.
Why It Happens: Stigg retries an error or timeout 3 times without backoff, then 3 times 30 seconds apart. After that the event is marked failed. Stigg stores failed events and can resend them on request, but there is no self-service log yet, and events that happen while a webhook is disabled are never sent at all.
Workarounds:
- Keep the receiver's job small: check the secret, store the event, return
200. - Alert on consecutive failures, as Stigg recommends, and contact Stigg to resend events after an incident.
- Don't disable a webhook to pause traffic. Missed events can't be replayed when it is re-activated.
How Hookdeck Can Help: Hookdeck accepts each delivery as soon as it arrives and stores it, then retries delivery to your service on a schedule you control and lets you replay anything that failed. Pausing a Hookdeck connection holds events rather than dropping them.
Events arrive out of order
The Problem: A subscription.updated can be processed before the subscription.created it follows.
Why It Happens: Stigg doesn't guarantee delivery order, and retries make reordering more likely.
Workarounds:
- Compare timestamps on the entity, or fetch the latest state from the Stigg REST or GraphQL API, before you overwrite local data.
- Make each handler safe to run before or after its neighbours.
How Hookdeck Can Help: Hookdeck records when each event arrived and lets you replay events individually or in bulk when you need to rebuild state.
messageId is not on every example
The Problem: Deduplication that relies on messageId can fail on some event types.
Why It Happens: Stigg's webhook page says every event has a messageId, but several of the examples in its OpenAPI, including subscription.trial_ and the product, credit_grant and automatic_recharge events, show none.
Workarounds:
- Deduplicate on
messageIdwhen it is present. - Fall back to a key built from
type, the entity'sidorentityId, and a timestamp field.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeated deliveries based on whichever body fields you choose.
Two vocabularies for credit events
The Problem: Your handler matches credit events by names that never arrive.
Why It Happens: Stigg's Credits page names events such as credits.granted, credits.balance.usage_ and credit_type.created, while the webhook OpenAPI uses credit_grant.* and credit_balance.* and has no credit type events.
Workarounds:
- Subscribe to the credit events you need, send a test event, and log the
typeyou receive. - Route unknown types to a log rather than an error.
How Hookdeck Can Help: Hookdeck shows every request it receives, with its full body, so you can see exactly which type values Stigg sends before you write filters for them.
Best practices
Compare the secret in constant time
Use crypto.timingSafeEqual or hmac.compare_digest rather than === or ==, and check lengths first in Node. Fail closed if the secret isn't configured.
Route on type and keep unknown events harmless
Stigg adds events over time. Switch on type, handle the ones you need, and acknowledge the rest with 200.
Use entitlements.updated for access decisions
If your product gates features, entitlements.updated carries the customer's effective entitlements after a change, whatever caused it (trigger). Recomputing access from that event is simpler than inferring it from subscription, add-on and promotion events separately.
Acknowledge fast and process asynchronously
Stigg allows 30 seconds, but a fast acknowledgement keeps you clear of the retry schedule. See why to process webhooks asynchronously and our guide to webhook idempotency.
Conclusion
Stigg authenticates webhooks with a shared secret in the Stigg-Webhooks-Secret header rather than a signature. Compare it in constant time over HTTPS, route on type, deduplicate on messageId with a fallback key, expect events out of order, and acknowledge quickly, because Stigg stops retrying after about a minute and a half.
Hookdeck Event Gateway sits in front of your receiver, checks the Stigg secret, and holds each event until your service has processed it.
Get started with Hookdeck for free and handle Stigg webhooks reliably in minutes.