Most engineering teams build the receiving side of webhooks first, because their own integrations force them to. Then a customer asks for webhooks from your product, and a two-day task appears on the roadmap: serialize an event, POST it to a URL, retry a few times. That estimate holds until the first enterprise tenant points an endpoint at a server that takes 30 seconds to answer, or until a customer asks, reasonably, "how do we know this request really came from you?"
Outbound webhook delivery is a distributed system with an untrusted network edge, customer-controlled destinations, and a promise that other companies build their workflows on. This article is written for the solution architect who must decide how much of that system to build and what to guarantee. It covers the delivery contract, a proposed reference design, signing and SSRF defenses, tenant-safe idempotency, failure patterns, and a set of mandatory go/no-go controls.
It complements our durable webhook receiver design, which covers the consuming side. Here we cover the provider side, where the failure modes are different and the blast radius is your whole customer base.
A note on scope: all code below is illustrative. It shows invariants, not a drop-in implementation, and it is not tied to any specific vendor API.
Key Takeaways
- Promise at-least-once delivery, not exactly-once. Publish a contract: stable event ID, no ordering guarantee, consumers must deduplicate, and a reconciliation API exists for missed events.
- Separate the event from the delivery. Store one immutable event (with a payload hash) and one delivery record per tenant, endpoint, and event. Write both atomically with the business change, or use a recoverable outbox.
- Define states honestly. A 2xx response recorded under a valid lease is acknowledged receipt. A timeout is unknown, not success and not verified failure. Terminal failure must be an explicit, visible state.
- Claim work with conditional writes. Read-compare-then-write is not compare-and-swap. Use one atomic statement plus a fencing token so a stale worker cannot overwrite a newer outcome.
- Isolate by endpoint. Per-endpoint concurrency caps, timeouts, and circuit breakers keep one slow customer from starving everyone else.
- Treat customer URLs as hostile input. Sign every request, and defend against SSRF with address validation, no redirects, and egress controls.
- Use all-required controls, not a score. The go/no-go table in this article is a floor for review. It does not certify production readiness or compliance.
Why Outbound Webhooks Become an Operations Product, Not a Feature
Key takeaway: Once customers build workflows on your events, delivery behavior is part of your API contract. Decide and publish that contract before you write the sender.
A webhook is an HTTP request sent from your infrastructure to a URL a customer typed into a settings page. That one sentence contains four separate engineering problems: you are making network calls to destinations you do not control, you are making promises about reliability to people who cannot see your internals, you are carrying tenant data across a trust boundary, and you are creating a new category of support tickets ("we never got the event").
The community-maintained Standard Webhooks specification frames the underlying issue plainly: webhooks are just HTTP requests from an unknown source, so authenticity verification is a requirement for any secure implementation. The same spec treats the webhook identifier as stable across retries so consumers can use it as an idempotency key. Those two ideas, authenticity and stable identity, anchor everything that follows.
The delivery contract you can actually keep
The most common design error is promising more than a network allows. Publish a contract you can enforce. The table below shows a defensible baseline.
| Contract element | What you can promise | What you should not promise |
|---|---|---|
| Delivery semantics | At-least-once attempts within a documented retry window | Exactly-once delivery |
| Identity | Same opaque event ID on every attempt and every replay of that event | That the ID is a secret or a safe join key across systems |
| Ordering | Per-resource version number so consumers can detect stale events | Global or per-tenant ordering |
| Acknowledgment | A 2xx response means the endpoint accepted the request | That the consumer finished processing |
| Failure visibility | Per-tenant delivery log with attempt outcomes and terminal states | That a failed delivery will be retried forever |
| Recovery | An API to list events by cursor so consumers can reconcile gaps | Indefinite event retention |
For a real-world reference point, Stripe's webhook documentation states that it attempts delivery for up to three days with exponential backoff in live mode, that it does not guarantee event ordering, and that consumers should track event IDs to detect duplicates. That is one vendor's published policy. It is a useful example of a clear contract, not a standard you must copy.
How Most Teams Build Webhook Delivery, and Why It Breaks at the Second Enterprise Customer
Key takeaway: The usual first version fails because it couples delivery to the request path, shares capacity across tenants, and records no durable proof of what was attempted.
Three architectures cover most of what we see in review. Each is rational at its stage, and each has a characteristic hidden failure.
| Dimension | A: Inline fire-and-forget | B: Shared queue with generic retry | C: Per-endpoint delivery ledger (proposed) |
|---|---|---|---|
| Build effort | Hours to days | Days to weeks | Weeks, plus ongoing operations |
| Delivery durability | Lost on crash or deploy | Durable if enqueue is atomic with the business write | Durable, with auditable per-attempt history |
| Tenant isolation | None; slow endpoints slow your API | Weak; one pool shared by all tenants | Per-endpoint caps, breakers, and quotas |
| Customer self-service | None | Log lines only | Delivery log, replay, endpoint health |
| Security posture | Often unsigned | Often one global signing secret | Per-endpoint secrets, rotation overlap, SSRF controls |
| Hidden failure mode | Events silently dropped; request latency tied to customer servers | Head-of-line blocking; retry storms; unknown outcomes treated as success | Operational overhead; needs clear ownership and runbooks |
Why intelligent teams still end up with A or B
Because the failures are rare early and expensive later. With ten tenants, a shared worker pool looks fine. With a few hundred, one misbehaving endpoint is statistically guaranteed. The design review that would have caught it never happens, because webhooks were scoped as a feature, not as a delivery platform.
Build, adopt, or buy
Managed webhook-sending services and open-source senders exist, and some implement the Standard Webhooks scheme. They can be the right call when webhooks are not your product's core. Evaluate them against the controls later in this article, and verify their tenancy model, payload retention, regional processing, and data-processing terms from primary documentation. A vendor's security feature supports your compliance work; it does not by itself satisfy your legal obligations, which depend on your data, your customers, and your jurisdictions.
A Proposed Reference Design: Event Ledger, Delivery Ledger, and Fenced Workers
Key takeaway: Capture events atomically, fan out to one delivery record per endpoint, claim work with a single conditional statement, and write outcomes only if your lease is still current.
The following is a proposed design, not a description of a product we ship. It assembles well-known patterns: a transactional outbox, a per-endpoint work ledger, and lease-based claiming with fencing.
Business transaction (tenant T) |- write domain change |- write event row (tenant_id, event_id, type, resource_id, | resource_version, payload_bytes, payload_sha256) |- write delivery rows, one per subscribed endpoint | (tenant_id, endpoint_id, event_id, state='pending') COMMIT (all or nothing) Delivery workers (stateless) claim one due delivery via atomic conditional UPDATE (lease + fence) check per-endpoint concurrency cap and circuit breaker sign stored bytes, POST with short timeout, no redirects record outcome only if (lease_owner, fence) still match Operator and tenant surfaces delivery log, replay, endpoint health, events list API (cursor)Atomic capture, or an explicitly recoverable path
If the event is published to a broker after the business transaction commits, a crash between the two loses the event. Either write event and delivery rows in the same database transaction as the business change, or use the outbox pattern so a relay can recover unpublished rows. The test is simple: kill the process after commit and before any enqueue, then confirm the event is still delivered.
Delivery states with explicit uncertainty
| State | Meaning | Next action |
|---|---|---|
| pending | Created, no attempt yet | Claim and attempt |
| in_flight | A worker holds a lease | Wait for outcome or lease expiry |
| acknowledged | A 2xx response was received and durably recorded under a valid fence | None; this is receipt, not proof of processing |
| retry_scheduled | A definite non-success response (for example 429 or 5xx) was received | Retry after backoff with jitter, honoring Retry-After where present |
| unknown | Request may or may not have been processed (timeout, connection reset after send) | Retry with the same event ID, and surface as unconfirmed in the delivery log |
| failed_terminal | Retry window exhausted, endpoint disabled, or payload expired | Visible to tenant; recoverable only via explicit replay or reconciliation |
The word unknown matters. It means completion could not be confirmed. It is neither success nor verified failure, and your dashboards, alerts, and customer-facing logs should keep it distinct. For a deeper treatment of this principle on the calling side of consequential APIs, see our guide to ambiguous timeouts and reconcile-before-retry.
One nuance deserves precision. Re-sending a notification after a timeout is acceptable here only because the contract requires consumers to deduplicate on a stable event ID and because every attempt carries the same immutable bytes. If your webhook handler on your own side triggers a consequential mutation (for example, an internal replay that changes tenant state), do not blindly retry. Reconcile the authoritative operation state first.
Atomic claim and fenced result write (illustrative, PostgreSQL-style)
-- Claim: one statement, no read-then-write gap WITH candidate AS ( SELECT tenant_id, endpoint_id, event_id FROM deliveries WHERE state IN ('pending', 'retry_scheduled', 'unknown') AND next_attempt_at <= now() AND (lease_expires_at IS NULL OR lease_expires_at < now()) ORDER BY next_attempt_at LIMIT 1 FOR UPDATE SKIP LOCKED ) UPDATE deliveries d SET state = 'in_flight', lease_owner = $1, lease_expires_at = now() + interval '30 seconds', fence = d.fence + 1, attempt_count = d.attempt_count + 1 FROM candidate c WHERE d.tenant_id = c.tenant_id AND d.endpoint_id = c.endpoint_id AND d.event_id = c.event_id RETURNING d.; -- Record outcome: conditional on the lease we still hold UPDATE deliveries SET state = $5, last_http_status = $6, next_attempt_at = $7 WHERE tenant_id = $2 AND endpoint_id = $3 AND event_id = $4 AND lease_owner = $1 AND fence = $8; -- 0 rows updated: lease was lost. Discard this outcome; do not retry the write.The claim is a single atomic statement, so two workers cannot take the same row. The result write is a target-side conditional mutation: it succeeds only if the owner and fence still match. A worker that stalled during a garbage-collection pause and resumes after its lease expired will update zero rows. Our article on worker leases and fencing tokens explains why a lease alone is insufficient without the fence.
Note the limit: fencing protects your ledger. It cannot stop a stalled worker from having already sent the HTTP request. That is exactly why the receiver contract demands deduplication on the event ID.
Signing, SSRF Defense, and Payload Privacy
Key takeaway: Sign id, timestamp, and exact bytes with a per-endpoint secret. Treat every customer URL as a potential attack on your network. Send the minimum data, and name an owner for payload retention.
Request signing
The Standard Webhooks specification defines a scheme worth adopting or at least studying. It sends three headers (webhook-id, webhook-timestamp, webhook-signature) and signs the string msg_id.timestamp.payload. It supports HMAC-SHA256 (symmetric) and ed25519 (asymmetric), carries multiple space-delimited signatures to allow zero-downtime secret rotation, and recommends unique signing keys per endpoint. The timestamp reflects the attempt, so a retry hours later has a fresh timestamp while the ID stays the same.
import hmac, hashlib, base64 def sign_v1(secret: bytes, msg_id: str, ts: int, body: bytes) -> str: # Illustrative. Secret handling and encoding must follow your chosen spec. if '.' in msg_id: raise ValueError('msg_id must not contain a dot') signed = f'{msg_id}.{ts}.'.encode() + body # body = the exact stored bytes digest = hmac.new(secret, signed, hashlib.sha256).digest() return 'v1,' + base64.b64encode(digest).decode()Three practical rules follow. First, sign and send the same stored bytes; re-serializing JSON at send time is a classic cause of signature mismatches. Second, keep the secret per endpoint, never global, so one tenant's leaked secret does not affect another. Third, publish a rotation procedure and test it, because rotation is where outages happen.
Scope of this standard: it addresses authenticity and replay resistance for HTTP webhook transport. It does not authorize which tenant may receive which event (that is your authorization layer), it does not protect data after the consumer receives it, and adopting it is not a compliance certification.
SSRF: the customer-supplied URL problem
When a tenant can enter any URL, your delivery workers become a proxy into your own network. The OWASP Server-Side Request Forgery Prevention Cheat Sheet is the reference to review with your security team. The design consequences for a webhook sender are:
- Require HTTPS for production endpoints and validate the destination at registration and at every send, since DNS answers change.
- Resolve the hostname, reject loopback, link-local, private, and metadata-service ranges, then connect to the validated address rather than resolving a second time.
- Do not follow redirects. Treat a 3xx as a non-delivery and show it in the delivery log.
- Run workers in a network segment with egress controls, ideally through a dedicated egress proxy, so a validation bug cannot reach internal services.
- Cap response body reads and total request time; never reflect the response body into logs without truncation and redaction.
def validate_destination(url, resolver, is_public_ip): # Illustrative only. Production code needs full IPv4/IPv6 handling, # IPv4-mapped IPv6, and an egress proxy as a second layer. parsed = parse_url(url) if parsed.scheme != 'https': raise Reject('https required') addrs = resolver(parsed.host) if not addrs or not all(is_public_ip(a) for a in addrs): raise Reject('non-public destination') return addrs[0] # connect to this address; send Host/SNI for parsed.host # allow_redirects = FalsePayload design and privacy ownership
Identifiers can themselves be sensitive. Sequential event IDs reveal your event volume; IDs that embed a tenant ID or user email leak to anyone who sees logs. Use opaque, random identifiers, and treat them as non-secret but non-guessable. Then choose between two payload styles:
| Style | Benefits | Risks and costs |
|---|---|---|
| Thin event (type, IDs, version) | Less sensitive data in transit and in logs; consumer fetches current state | Extra API calls; consumer needs API credentials and rate-limit headroom |
| Fat event (full resource snapshot) | Fewer round trips; works for consumers without API access | Personal or confidential data copied to customer endpoints, your attempt logs, and any replay archive |
Whichever you pick, assign privacy ownership explicitly. One named team should own retention of event payloads, attempt logs, dead-letter storage, and replay archives, and your verified erasure workflow must reach all of them. Once a payload is delivered, it lives in the customer's systems and you cannot erase it there; say so in your documentation. Which legal duties apply to you depends on your data, customers, and jurisdictions, so involve counsel rather than inferring obligations from an engineering article.
Tenant-Safe Idempotency, Replay, and Ordering
Key takeaway: Scope every key by tenant, bind it to an immutable payload hash, reject key reuse with different content, and keep legitimate later operations (replays, new versions) as distinct, linked records.
Idempotency in webhook senders has three separate layers, and teams often collapse them into one. Our idempotency framework covers the general case; the table below maps it to this problem.
| Layer | Identity | Invariant |
|---|---|---|
| Event creation | (tenant_id, event_id), or (tenant_id, source_operation_id) when events come from API calls | One event per source operation; payload hash fixed at creation |
| Delivery | (tenant_id, endpoint_id, event_id) | Exactly one delivery row; unique constraint enforces it |
| Replay request | (tenant_id, replay_key) | Key bound to hash of (event_id, endpoint_id, payload_sha256); same key with different content returns a conflict |
Why tenant scope is non-negotiable
If an idempotency key or endpoint ID is unique only globally, tenants can probe each other's identifiers or collide with them. Resolve every endpoint, event, and replay request through the authenticated tenant first. Return "not found" rather than "forbidden" for another tenant's IDs. Our multi-tenant isolation guide discusses how row-level and schema-level isolation change the enforcement options.
Immutable payloads and changed-payload rejection
Store the exact bytes and a SHA-256 at event creation. Every attempt, retry, and replay sends those bytes. If a caller reuses a replay key or a source-operation key with a different hash, reject it. Do not silently overwrite the stored payload, because a consumer that already processed the first version would have no way to detect the change.
Legitimate later operations are new records
- Redelivery of the same event: another attempt on the same delivery row, same event ID, same bytes.
- Operator replay after an outage: a new attempt linked to a replay operation record, same event ID, same bytes, so consumers deduplicate correctly.
- The resource changed again: a new event with a new ID and a higher
resource_version. This is a different fact, not a duplicate. - A corrected payload: a new event that references the original through a causal link field. Never mutate history.
Ordering without false promises
Retries, parallel workers, and per-endpoint backoff all reorder events. Rather than promising order, include resource_id and resource_version, and tell consumers to ignore events older than the version they already hold or to fetch current state. The Stripe documentation referenced earlier takes the same stance: it does not guarantee ordering and suggests retrieving objects through the API when needed.
The reconciliation API
The most valuable feature you can ship after delivery itself is a cursor-based events list: events created after a given cursor, filterable by type and by delivery status per endpoint. It turns every outage from a support escalation into a consumer-run recovery job. Publish its retention window, because that window is the true upper bound on your recovery promise.
Why This Fails in the Real World
Key takeaway: Capable teams fail here because of shared capacity, missing terminal states, and ungoverned rotation, not because of bad code. The scenarios below are illustrative composites, not customer results.
Failure pattern 1: One slow endpoint starves everyone
Illustrative scenario: A shared pool of 50 workers has a 30-second request timeout. One tenant's endpoint begins hanging and receives 10 events per second. By Little's law, concurrent in-flight requests equal arrival rate times duration: 10 per second times 30 seconds is 300 concurrent attempts wanted from a pool of 50. The pool saturates, and unrelated tenants see multi-minute delays. Nothing is "down"; latency simply rises everywhere.
Why smart teams miss it: load tests use healthy synthetic endpoints. The governance gap is that no one owns a per-tenant fairness requirement. Controls: per-endpoint concurrency caps, a short timeout, a circuit breaker that opens on consecutive failures, and per-tenant queue fairness.
Failure pattern 2: The retry storm after a customer outage
Illustrative scenario: A customer's endpoint is down for six hours. Every pending event retries on the same fixed interval, and when the endpoint returns, thousands of attempts arrive at once. The customer's server falls over again and the cycle repeats.
Why it happens: backoff was configured per attempt, not per endpoint, and no one defined when to stop. Controls: exponential backoff with jitter at the endpoint level, honoring Retry-After on 429 and 503, a ramp-up rate after circuit recovery, and an automatic disable-and-notify policy with a documented threshold. Pair this with a controlled redrive design so recovery is rate-limited too.
Failure pattern 3: Timeouts counted as success, and silent expiry
Illustrative scenario: A worker treats "no exception raised" as delivered. Timeouts after the request was sent are marked complete, and events that exhaust their retry window are deleted by a cleanup job. A customer reports missing orders, and the delivery log shows green.
Why it happens: the state model had two outcomes, success and retry. Controls: the explicit unknown and failed_terminal states, a rule that only a recorded 2xx under a valid fence yields acknowledged, and a prohibition on deleting terminal records before the published retention window ends.
Failure pattern 4: Secret rotation and re-serialization outages
Illustrative scenario: Security rotates a signing secret after a suspected leak. The sender switches immediately, every consumer that has not updated rejects all webhooks, and support is flooded. In a separate incident, a library upgrade changes JSON key ordering at send time, and signatures stop verifying for a subset of customers.
Controls: dual-signature overlap during rotation (as the Standard Webhooks scheme supports), a rotation runbook that is rehearsed, and signing stored bytes rather than a re-serialized object.
Failure pattern 5: The redirect to the metadata service
A tenant registers a URL that passes validation, then changes DNS to point at an internal address, or returns a redirect to one. Without per-send validation, pinned connections, and egress controls, workers fetch internal resources. This is the textbook case behind the OWASP guidance cited earlier, and the governance gap is usually that the security review happened at registration only.
Go/No-Go Controls, Rollout Path, and the Annual Review
Key takeaway: Every control below is required. A single unmet control is a no-go for that scope. Passing all of them is a floor for review, not certification of production readiness or compliance.
Mandatory go/no-go controls
| # | Required control | Verification |
|---|---|---|
| 1 | Event and delivery rows are committed atomically with the business change, or an outbox recovers unpublished rows | Kill the process between commit and enqueue; the event still delivers |
| 2 | Payload bytes and SHA-256 are immutable after creation; key reuse with a different hash is rejected | Automated test sends a conflicting replay key and expects a conflict response |
| 3 | Unique constraint on (tenant, endpoint, event); work claimed by one atomic conditional statement with a fencing token | Run many concurrent workers against a small backlog; no duplicate in-flight claims |
| 4 | States include unknown and failed_terminal; no outcome is dropped silently | Inject timeouts and window expiry; both appear in the tenant delivery log |
| 5 | Per-endpoint concurrency cap, timeout, and circuit breaker | Hang one test endpoint; other tenants' latency stays within target |
| 6 | Per-endpoint signing secrets, signed timestamp and ID, tested rotation overlap | Rotate in staging while consumers verify both signatures |
| 7 | SSRF controls: public-address validation at send time, no redirects, egress restrictions | Tests against private ranges, redirects, and DNS changes after registration |
| 8 | Published consumer contract: at-least-once, no ordering guarantee, dedupe on event ID, 2xx means receipt | Contract is in public docs and linked from the endpoint settings page |
| 9 | Cursor-based events list for reconciliation, with stated retention | Consumer-side recovery drill using only the public API |
| 10 | Named owner for payload and log retention; deletion workflow covers events, attempts, dead letters, and replay archives | Erasure test confirms no residual copies in your systems |
| 11 | Tenant-scoped authorization on every endpoint, event, and replay lookup | Cross-tenant access tests return not found |
| 12 | Alerts on unknown-state growth, terminal failures, breaker openings, and queue age per tenant | Alert fires in a game-day exercise and reaches an on-call owner |
A lower-risk rollout path
- Internal consumers first. Dogfood with your own endpoints to validate the contract and the delivery log.
- Design partners with named contacts. Choose a few tenants willing to report issues, and run a deliberate outage drill with them.
- Gradual enablement per tenant tier. Use quotas and per-tenant feature flags so you can cap exposure.
- Operate with an explicit runbook. Cover stuck leases, mass-failure events, secret rotation, and replay approvals before general availability.
Teams that lack the capacity to build and operate this can bring in experienced platform and integration engineers. Our API integration services page outlines how Developers.dev engineers support this kind of work.
Shipping webhooks to enterprise tenants soon?
A delivery platform that loses events or leaks across tenants is expensive to retrofit. Review the design before the first customer depends on it.
Talk to Developers.dev engineers about a webhook delivery design review.
Contact Us2027 Update: what to re-check each year, framed for longevity
Specifications and platform behavior evolve, so build a yearly review into your calendar rather than assuming today's guidance holds. Use this checklist:
- Re-read the Standard Webhooks specification for version changes, and confirm your signing scheme and library versions still match.
- Re-check the OWASP SSRF guidance and re-run destination-validation tests, including new address formats your resolver may encounter.
- Review any vendor sender or platform documentation you depend on for changed retry windows, retention limits, or regional processing terms.
- Audit retention of payloads and logs against your current data inventory and customer commitments.
- Run a recovery drill using only the public reconciliation API, and measure the time a customer would need.
The fundamentals in this article (immutable events, explicit uncertainty, conditional claims, tenant-scoped keys, hostile-URL handling) do not depend on any one year or vendor.
Conclusion: Decide the Contract First, Then Build to It
Outbound webhooks fail in predictable ways, and almost every failure traces back to a missing decision rather than a missing library. Use the following actions as your next steps.
- Write the consumer contract this week. Cover at-least-once delivery, event ID deduplication, no ordering guarantee, what a 2xx means, retry window, and reconciliation retention. Get engineering, support, and security to sign off.
- Audit your current sender against the 12 go/no-go controls. List every unmet control as a blocker for enterprise tenants, not as backlog polish.
- Replace implicit outcomes with explicit states. Add unknown and failed_terminal, expose them to tenants, and stop deleting terminal records early.
- Isolate by endpoint before you scale by tenant count. Add concurrency caps, short timeouts, and circuit breakers, then run a hanging-endpoint drill.
- Name a privacy and retention owner. Decide thin or fat payloads deliberately, and make sure deletion reaches every copy you hold.
About the authors and reviewers
Developers.dev is an offshore software development and staff augmentation company founded in 2007, headquartered in Indore, India, with engineering teams that build and operate cloud-native, API-heavy systems for clients in the USA, EMEA, and Australia. The company lists CMMI Level 5, ISO 27001, and SOC 2 among its accreditations; the scope of any certification should be confirmed from the certificate itself, and it does not make a client's own system compliant. Cloud, DevOps, and integration practitioners on our team contribute to our engineering content.
Reviewed by the Developers.dev Expert Team.
Frequently Asked Questions
Should outbound webhooks promise exactly-once delivery?
No. Over an unreliable network, a sender cannot distinguish a request that was processed but whose response was lost from one that never arrived. The defensible promise is at-least-once attempts with a stable event ID, so consumers can deduplicate and achieve effectively-once processing on their side.
Is a timeout a failed delivery?
Not necessarily. A timeout after the request was sent means completion could not be confirmed. Record it as unknown, retry using the same event ID and identical bytes, and make sure your contract requires consumers to deduplicate. Do not report it as success or as verified failure.
What should the sender do with 3xx redirects?
Do not follow them. Following redirects can steer your workers to internal addresses and bypass destination validation. Treat a 3xx as non-delivery, log it, and let the tenant correct the endpoint URL.
How should we sign webhook requests?
Sign the event ID, a per-attempt timestamp, and the exact payload bytes, using a per-endpoint secret. The Standard Webhooks specification describes one such scheme with HMAC-SHA256 or ed25519, multiple signatures for rotation, and a timestamp tolerance for replay protection. It covers transport authenticity only, not tenant authorization or compliance.
Thin payloads or full payloads?
- Thin events reduce sensitive data in transit and in your logs, but require consumers to call your API.
- Full payloads reduce round trips but copy data to more places, which raises retention and deletion obligations.
Choose deliberately, document it, and assign an owner for retention either way.
How do we let customers recover missed events?
Provide a cursor-based events list API with a published retention window, plus delivery status per endpoint. Consumers can then reconcile gaps after an outage without opening a support ticket. Vendors such as Stripe document similar list-based recovery for undelivered events.
When should we disable a failing endpoint?
Define a documented threshold, such as sustained consecutive failures across a stated time window, then disable the endpoint and notify the tenant. Decide in advance whether events created while disabled remain available for replay or reconciliation, and state that policy in your documentation.
Does using a managed webhook service remove our responsibilities?
No. A managed service can supply signing, retries, and logs, but you still own tenant authorization, payload content, retention decisions, and your legal obligations. Verify the provider's tenancy model, data retention, and processing terms from its primary documentation.
Your webhooks are now part of your customers' production systems.
Lost events, cross-tenant leaks, and retry storms are design problems, and they are cheaper to fix before launch than after the first escalation.
Work with Developers.dev integration and platform engineers to design, build, or review your webhook delivery architecture.
Contact UsPlatform Engineering Services
This guide is designed for engineering leaders who want to plan a practical implementation. Use the related Developers.dev path to compare delivery options, implementation fit, risk, and practical next steps.
Reviewed by the Experts team
This guide is reviewed for clarity, technical and operational relevance, service alignment, and a useful next step. Verified by our SEO team for clear search presentation.
Reviewed by the Experts team. Verified by our SEO team. Validate legal, security, data, budget, and operational requirements with the relevant stakeholders before rollout.

