Prompt
I’m building a multi-tenant app and need per-customer webhook delivery logs
Latest observation
A good pattern for per-customer webhook delivery logs in a multi-tenant app is to treat webhook delivery as its own first-class entity, with every record scoped to a tenant/customer.
Core ideas
1) Store each delivery attempt
Keep a row for every webhook attempt, not just every event. This lets you track retries, failures, and final outcomes.
Typical fields:
idtenant_id/customer_idevent_id(the business event being sent)subscription_idorendpoint_iddelivery_status(pending,success,failed,retrying)attempt_numberhttp_statusresponse_bodyor truncated response snippetrequest_headersrequest_payloador payload referencestarted_atfinished_atnext_retry_aterror_messagesignature_versioncreated_at
2) Separate event from delivery attempt
A single event may go to multiple endpoints, and each endpoint may retry multiple times.
Suggested structure:
- webhook_events: one business event generated by your app
- webhook_deliveries: one row per endpoint/customer target per event
- webhook_attempts: one row per retry attempt, if you want detailed history
If you want simplicity, you can store attempts directly in one webhook_deliveries table with a attempt_count and append-only attempt metadata. If you need auditing/debugging, a separate webhook_attempts table is better.
Multi-tenant scoping
Every table should include:
tenant_id- indexes starting with
tenant_id - authorization checks that always filter by
tenant_id
Example indexes:
(tenant_id, created_at desc)(tenant_id, delivery_status, created_at desc)(tenant_id, endpoint_id, created_at desc)
If tenants can only view their own logs, enforce this at:
- application layer
- database row-level security if available
Recommended schema example
webhook_events
idtenant_idevent_typeresource_typeresource_idpayload_jsoncreated_at
webhook_endpoints
idtenant_idurlsecretis_activecreated_at
webhook_deliveries
idtenant_idevent_idendpoint_idstatusattempt_countlast_http_statuslast_errorlast_attempt_atnext_retry_atcreated_atupdated_at
webhook_attempts
idtenant_iddelivery_idattempt_numberrequest_sent_atresponse_received_athttp_statuserror_messageresponse_bodyduration_ms
Operational tips
Keep payloads manageable
Store:
- full payload if small, or
- compressed payload, or
- object storage pointer
For logs, often it’s enough to store:
- payload hash
- a truncated response
- headers
- error summary
Add retention policies
Webhook logs can grow fast. Common approach:
- keep detailed attempts for 30–90 days
- keep summary rows longer
- archive old records to cheaper storage
Consider privacy/security
Webhook payloads may include PII or secrets.
- redact sensitive fields
- encrypt at rest
- avoid storing secrets in plaintext
- limit who can view logs
Build tenant-safe querying
All log queries should be tenant-scoped:
WHERE tenant_id = ?- no cross-tenant joins without explicit admin access
UI ideas
For each customer, show:
- list of recent webhook events
- delivery status
- endpoint target
- retry count
- last error
- timestamps
- drill-down into attempt history
If you want the simplest workable design
Start with just two tables:
webhook_eventswebhook_deliveries
Add webhook_attempts later if retries/auditing become important.
If you want, I can give you:
- a concrete SQL schema,
- a Prisma/Sequelize/Django model version,
- or an API design for exposing these logs per tenant.