Webhook Server
A webhook endpoint accepts requests from systems you do not control.
This changes the threat model.
Webhook requests may be:
- Retries of the same event
- Delivered out of order
- Sent with invalid payloads
- Spoofed or replayed
- Temporarily bursty
- Sent long after the original event occurred
A correct webhook server must treat every request as hostile by default.
Plumego provides the structure to do this explicitly.
Core Design Principles
A reliable webhook server must implement:
- Authentication / verification β is the sender legitimate?
- Integrity validation β was the payload tampered with?
- Idempotency β can the same event be processed multiple times safely?
- Explicit failure semantics β should the sender retry?
- Isolation from domain logic β transport concerns stay at the edge
None of these are optional in production systems.
Webhook Lifecycle (High Level)
A typical Plumego webhook request flows like this:
Incoming Webhook Request
β Trace ID Middleware
β Logging Middleware
β Panic Recovery
β Webhook Verification Middleware
β Handler
β Usecase
β Domain
Verification happens before any business logic is executed.
Step 1: Define the Webhook Contract
Before writing code, define the contract:
- Endpoint path (e.g.
/webhooks/provider-x) - HTTP method(s)
- Signature scheme
- Required headers
- Payload format
- Retry behavior on non-2xx responses
This contract is part of your systemβs external API and must be treated as stable.
Step 2: Signature Verification Middleware
Signature verification must be centralized.
Conceptual example:
func WebhookSignatureMiddleware(secret []byte) plumego.Middleware {
return func(ctx *plumego.Context, next plumego.NextFunc) {
signature := ctx.Request().Header.Get("X-Signature")
if signature == "" {
ctx.JSON(http.StatusUnauthorized, errorResponse("missing signature"))
return
}
body := ctx.RawBody()
if !verifySignature(body, signature, secret) {
ctx.JSON(http.StatusUnauthorized, errorResponse("invalid signature"))
return
}
next()
}
}
Key properties:
- Verification happens before handler execution
- Raw body is used (no mutation before verification)
- Failure stops request processing immediately
Never verify signatures inside handlers.
Step 3: Parsing the Payload Explicitly
Webhook payloads should be parsed only after verification.
Example (handler-level):
var event ProviderEvent
if err := ctx.BindJSON(&event); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse("invalid payload"))
return
}
Do not assume payload correctness.
Validation errors must result in explicit failure responses.
Step 4: Idempotency Handling
Most webhook providers retry aggressively.
Your system must be idempotent.
Common idempotency strategies
- Event ID de-duplication
- Idempotency keys
- Payload hash storage
Example (conceptual usecase):
if store.Exists(event.ID) {
return nil // already processed
}
processEvent(event)
store.MarkProcessed(event.ID)
Idempotency belongs in the application layer, not middleware.
Step 5: Failure Semantics and Retries
Webhook providers interpret responses differently.
General guidance:
- 2xx β event accepted, no retry
- 4xx β client error, often no retry
- 5xx β server error, retry expected
Be intentional:
- Validation failures β 400
- Signature failures β 401 / 403
- Temporary failures β 500
Accidental 2xx responses can cause data loss.
Step 6: Keep Handlers Thin
Webhook handlers should:
- Validate payload structure
- Invoke a usecase
- Return an appropriate response
They should not:
- Perform complex business logic
- Handle retries manually
- Encode provider-specific logic deep in the system
Translate at the boundary.
Webhook Security Considerations
Additional safeguards often include:
- IP allowlists (when feasible)
- Timestamp validation to prevent replay
- Rate limiting
- Separate webhook secrets per provider
Security layers should be additive, not exclusive.
Handling Large Payloads
Some providers send large payloads.
Mitigations include:
- Request size limits
- Streaming parsing (if necessary)
- Early rejection before allocation
Do not blindly read unbounded request bodies.
Observability for Webhooks
Webhook endpoints require strong observability:
- Always log Trace ID
- Log provider event ID
- Log verification failures
- Track retry patterns
This is essential for diagnosing integration issues.
Testing Webhook Handling
You should test:
- Valid signature + valid payload
- Invalid signature
- Replayed events
- Out-of-order delivery
- Malformed payloads
Webhook bugs often appear only under retry conditions.
Common Mistakes
Verifying After Parsing
Parsing mutates the body stream.
Always verify first.
Treating Webhooks Like Normal APIs
Webhook senders do not behave like clients.
Retry behavior must be assumed.
Encoding Provider Logic in Domain Code
Provider-specific quirks must stay at the boundary.
Summary
In Plumego, a correct webhook server is:
- Explicitly verified
- Idempotent by design
- Clear in failure semantics
- Isolated from core logic
- Observable under failure
Webhook handling is integration work β
and integration work must be defensive.
Next
With inbound integrations handled safely, the next guide is:
β WebSocket
This explains how to manage long-lived, stateful connections explicitly in Plumego.