Webhooks
A webhook represents one webhook integration — a target URL where HostWebhook delivers events, with optional additional targets, payload filters, payload transforms, and signature verification.
What is a webhook?
Each webhook has:
- Name—A friendly label shown in the dashboard
- Target URL—Where HostWebhook POSTs events (your server)
- Ingress URL—Where your webhook source sends events — unique per webhook
- Ingress token—The token in the ingress URL — rotate it to invalidate the old URL
- Signing secret—Used to sign every delivery — verify it on your server
- Max retries—How many additional attempts after a failure (plan-limited: Free 0–3, Pro 0–10, Enterprise unlimited)
- Retry delay—Base delay (seconds) for exponential backoff (default: 60)
- Rate limit—Max deliveries per minute (0 = unlimited)
- Active—Paused webhooks queue events but pause delivery
- Incoming signature—Verify signatures from Stripe, GitHub, or custom HMAC on inbound requests
- Payload filters—Rules to accept or discard events based on payload content
- Additional targets—Deliver each event to extra URLs in parallel with optional payload transforms
Plan limits
| Plan | Events / month | Max retries | Max retry delay | Backoff cap |
|---|---|---|---|---|
| Free | 1,000 | 3 | 60 s | 30 min |
| Pro | 50,000 | 10 | 3,600 s | 6 hr |
| Enterprise | Unlimited | Unlimited | Unlimited | 24 hr |
maxRetries: 3 means up to 4 total attempts (1 initial + 3 retries). Exceeding your plan's limit returns 403 Forbidden.Rotating secrets
Both the ingress token and signing secret can be rotated at any time from the webhook detail page.
- Rotating the ingress token invalidates the old ingress URL immediately. Update your webhook source to use the new URL.
- Rotating the signing secret invalidates the old secret immediately. Update your server's environment variable first.
Pausing a webhook
Pausing a webhook stops delivery. Events continue to be accepted at the ingress URL and queued — they will be delivered once you activate the webhook again. Useful for maintenance windows.
Ingress protection
HostWebhook offers multiple layers of ingress protection. You can enable any combination — when multiple are active, all must pass.
Require API Key
When enabled, every request to the ingress must include a valid API key with events:write scope in the Authorization header. The API key identifies who is sending, verifies they have permission, and confirms they belong to the same organization as the webhook.
curl -X POST https://api.hostwebhook.com/api/in/YOUR_TOKEN \
-H "Authorization: Bearer hwk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"event": "order.created"}'Incoming signature verification
Verifies that the payload was not tampered with in transit. Configure the type and shared secret — HostWebhook validates the signature header on every inbound request.
| Type | Description |
|---|---|
| none | No signature verification (default) |
| stripe | Verify Stripe-Signature header (HMAC-SHA256) |
| github | Verify X-Hub-Signature-256 header (HMAC-SHA256) |
| shopify | Verify X-Shopify-Hmac-Sha256 header (HMAC-SHA256, base64) |
| hostwebhook | Verify X-HostWebhook-Signature header (t=timestamp,v1=hmac) |
| static | Verify a static secret in Authorization, X-Webhook-Secret, or custom header |
API Key vs Incoming Signature
They solve different problems and can work together.
| API Key | Incoming Signature | |
|---|---|---|
| Who uses it | Your clients (SDK, API) | External services (Stripe, GitHub) |
| What it verifies | Identity + permissions (scopes) | Payload integrity (not tampered) |
| Scopes | Yes (events:write, canvas:write, etc.) | No |
| Org ownership | Yes | No |
| Replay protection | No (use deduplication) | Yes (timestamp in HMAC) |
stripe, GitHub → github).Payload filters
Payload filters let you discard events that don't match specific criteria — before any delivery attempt is made. Events that fail the filter are saved with status filtered and no delivery job is created.
payment_intent.succeeded events from a Stripe integration that sends dozens of event types.How filters work
All filter rules are evaluated against the parsed JSON payload. If all rules pass, the event is queued for delivery. If any rule fails, the event is discarded. Fields support dot-notation for nested properties.
Available operators
| Operator | Description | Value required |
|---|---|---|
| eq | Field equals value (string comparison) | Yes |
| neq | Field does not equal value | Yes |
| contains | Field contains value (string) | Yes |
| gt | Field is greater than value (numeric) | Yes |
| lt | Field is less than value (numeric) | Yes |
| exists | Field is present and not null | No |
| not_exists | Field is absent or null | No |
Example — only process succeeded payments
// Payload filters on the webhook:
[
{ "field": "type", "operator": "eq", "value": "payment_intent.succeeded" },
{ "field": "data.object.amount", "operator": "gt", "value": "0" }
]
// This Stripe payload passes:
{ "type": "payment_intent.succeeded", "data": { "object": { "amount": 5000 } } }
// This one is filtered (wrong type):
{ "type": "payment_intent.created", "data": { "object": { "amount": 5000 } } }Manage payload filters from the Payload Filters section on the webhook detail page.
Additional targets
Additional targets let you deliver each incoming event to multiple URLs in parallel — without needing multiple ingress tokens. Each target is independent: it has its own retry budget, active toggle, optional per-target payload filters, and an optional payload transform.
targetUrl on the webhook always receives the original payload. Additional targets are queued in parallel and can transform the payload before delivery. Additional target failures do not affect the event's primary lifecycle status.notifyPrimaryOnFailure on an additional target if you want your primary server to know when that target permanently failed — for example, to log that a welcome email was never sent. The notification is a best-effort POST with a type: "target.failed" payload and carries a valid X-HostWebhook-Signature if the webhook has a signing secret. See payload shape →When to use additional targets
- Mirror events to a staging environment and a logging service simultaneously
- Send the same event to multiple internal microservices
- Forward Stripe events to Discord with a formatted embed (via payload transform)
- Send a subset of events to an analytics pipeline using per-target filters
Additional target configuration
| Field | Description |
|---|---|
| url | The target URL to deliver to (required) |
| name | Optional friendly label |
| isActive | Toggle this target without removing it |
| maxRetries | Override the webhook's retry count for this target (plan-limited) |
| filters | Per-target payload filters — same operators as top-level filters |
| payloadTransform | JSON template with {{path}} interpolation — transforms the payload before delivery |
| notifyPrimaryOnFailure | When true: if this target exhausts all retries, HostWebhook POSTs a target.failed notification to the webhook's primary target URL |
Payload transforms
Payload transforms let you reshape the event before it reaches an additional target — converting a raw Stripe payload into a Discord embed, for example. Transforms use a JSON template with {{path}} interpolation.
Interpolation syntax
| Syntax | Description |
|---|---|
| {{payload.type}} | Value from the event payload using dot-notation |
| {{headers.stripe-signature}} | Value from the original inbound headers |
| {{meta.eventId}} | HostWebhook event metadata (eventId, receivedAt) |
| {{payload.amount ?? 0}} | Null coalescing — use fallback if path is null/undefined |
| {{payload.status === 'active' ? 'Yes' : 'No'}} | Ternary — compare with string/number/null |
| {{payload.field !== null ? payload.other : 'none'}} | Ternary with path resolution in branches |
Example — Stripe payment to Discord embed
{
"embeds": [{
"title": "💳 Payment Received",
"color": 5763719,
"fields": [
{
"name": "Amount",
"value": "{{payload.data.object.amount_received ?? 0}} {{payload.data.object.currency}}",
"inline": true
},
{
"name": "Customer",
"value": "{{payload.data.object.customer ?? 'Guest'}}",
"inline": true
}
],
"timestamp": "{{meta.receivedAt}}"
}]
}API
Additional targets are managed as separate resources via the /additional-targets API. See the API reference for the full schema.
Circuit Breaker
The circuit breaker automatically stops delivering to a webhook that is consistently failing — protecting both your server and your retry budget.
| Field | Description | Default |
|---|---|---|
| circuitBreaker.enabled | Enable the circuit breaker | false |
| circuitBreaker.threshold | Consecutive failures to open the circuit | 5 |
| circuitBreaker.cooldownSeconds | How long the circuit stays open before testing | 300 |
The circuit breaker has three states: CLOSED (normal — deliveries flow), OPEN (blocked — events are queued but not delivered), and HALF_OPEN (testing — one event is allowed through to check if the webhook has recovered).
Priority
Each webhook has a priority field (1–5, default 3). Priority 1 is critical — events for higher-priority webhooks are dequeued and delivered before lower-priority ones.
X-HW-Priority header on inbound requests. This overrides the webhook default for that specific event.Fallback URL
Configure a fallbackUrl on a webhook. If the primary targetUrl fails all retries, HostWebhook makes one final attempt to the fallback URL before marking the event as failed. Useful for disaster recovery or backup processing servers.
Chaos testing
Enable chaos testing to simulate failures in development and staging:
| Field | Description |
|---|---|
| chaosConfig.enabled | Enable chaos testing for this endpoint |
| chaosConfig.failureRate | Percentage of deliveries that will be artificially failed (0–100) |
| chaosConfig.latencyMs | Artificial latency added to each delivery (milliseconds) |