Webhook
Overview
The Webhook domain in comby is designed to manage webhooks, enabling systems to notify external services of events or changes in real time. Webhooks are a critical part of integration workflows, providing a mechanism for external systems to react to specific events by consuming data through HTTP endpoints. The Webhook aggregate models the configuration and state of a webhook, ensuring consistency and traceability through event sourcing.
Concept
What is a Webhook?
A Webhook is an HTTP callback that automatically sends event data to an external URL when a specific domain event occurs. Webhooks enable real-time integrations with external systems, third-party services, and custom applications without polling.
Key Characteristics:
- Event-Driven: Triggered automatically when domain events occur
- Dual-Scope: Can be tenant-scoped or workspace-scoped
- Event Filtering: Subscribe to specific domain events (e.g.,
Customer.CustomerCreatedEvent) - Active/Inactive: Can be enabled or disabled without deletion
- HTTP POST: Sends event data as JSON via HTTP POST
- Test Capability: Manual testing endpoint to verify webhook configuration
Event Occurs → Webhook Middleware → HTTP POST → External URLArchitecture
Hierarchy
System
└── Tenant
├── Webhooks (Tenant-scoped)
│ ├── DomainEvtIdentifier (event filter)
│ ├── WebhookUrl (target URL)
│ └── Active (on/off)
└── Workspaces
└── Webhooks (Workspace-scoped)
├── DomainEvtIdentifier
├── WebhookUrl
└── ActiveRelated Entities
Webhook is connected to:
- Tenant: Each webhook belongs to a tenant
- Workspace: Webhooks can optionally be scoped to a workspace
- All Domains: Webhooks can listen to events from any domain
- Events: Webhooks are triggered by domain events
Domain Model
type Webhook struct {
AggregateUuid string
// References
WorkspaceUuid string // Optional - for workspace-scoped webhooks
// Value Objects
Active bool
DomainEvtIdentifier string // e.g., "Customer.CustomerCreatedEvent"
WebhookUrl string // Target URL for HTTP POST
}Webhook Scopes
Tenant-Scoped Webhooks
Webhooks created at the tenant level receive events for all resources within the tenant:
Tenant Webhook:
├── Listens to: All events in tenant matching DomainEvtIdentifier
├── Receives: Events from all workspaces and tenant-level resources
└── Use Case: System-wide integrations, monitoring, loggingWorkspace-Scoped Webhooks
Webhooks created within a workspace only receive events from that workspace:
Workspace Webhook:
├── Listens to: Events in workspace matching DomainEvtIdentifier
├── Receives: Only events from specific workspace
└── Use Case: Project-specific integrations, team notificationsEvent Filtering
Webhooks filter events using the DomainEvtIdentifier format:
{Domain}.{EventName}Examples:
Customer.CustomerCreatedEvent- Customer creation eventsInvoice.InvoiceCreatedEvent- Invoice creation eventsIdentity.IdentityAddedGroupEvent- Group assignment eventsWorkspace.WorkspaceCreatedEvent- Workspace creation events
Matching Logic:
- Webhook only triggers if event's domain identifier matches exactly
- Webhook only triggers if event's tenant matches webhook's tenant
- For workspace-scoped webhooks, event must be from that workspace
HTTP POST Format
When a webhook is triggered, it sends an HTTP POST request:
Request:
POST {WebhookUrl}
Content-Type: application/json
Accept: application/json
{
"eventUuid": "event-uuid",
"aggregateUuid": "aggregate-uuid",
"tenantUuid": "tenant-uuid",
"workspaceUuid": "workspace-uuid",
"domain": "Customer",
"domainEvtName": "CustomerCreatedEvent",
"domainEvt": {
// Event-specific data
"name": "Acme Corp",
"email": "contact@acme.com"
},
"createdAt": 1638360000000000000,
"version": 1
}Structure
The Webhook aggregate extends the BaseAggregate, leveraging comby's core capabilities to manage state, track changes, and persist events. The aggregate defines fields that represent the attributes of a webhook:
References: Reserved for potential relationships with other entities (not explicitly included in the default implementation).
Value Objects:
- Active: A boolean indicating whether the webhook is active and capable of sending notifications.
- EventDataIdentifier: The identifier of the event data that the webhook is configured to process.
- WebhookUrl: The HTTP endpoint to which the webhook sends its notifications.
This structure allows the Webhook aggregate to model the configuration and status of webhooks effectively, ensuring compatibility with a wide range of integration scenarios.
Use Cases
1. External System Integration
E-Commerce Platform Integration:
├── Webhook: Customer.CustomerCreatedEvent
│ └── URL: https://crm.example.com/api/customers
├── Webhook: Invoice.InvoiceCreatedEvent
│ └── URL: https://accounting.example.com/api/invoices
└── Webhook: Invoice.InvoiceCreatedEvent
└── URL: https://email-service.example.com/api/send2. Team Notifications
Slack/Discord Notifications:
├── Workspace A Webhook: Workspace.WorkspaceInvitationCreatedEvent
│ └── URL: https://hooks.slack.com/services/workspace-a
├── Workspace B Webhook: Workspace.WorkspaceInvitationCreatedEvent
│ └── URL: https://hooks.slack.com/services/workspace-b
└── Tenant Webhook: Identity.IdentityCreatedEvent
└── URL: https://hooks.slack.com/services/admin-channel3. Audit & Compliance Logging
Audit System:
├── Webhook: *.* (All events)
│ └── URL: https://audit-system.example.com/api/events
├── Webhook: Identity.IdentityAddedGroupEvent
│ └── URL: https://compliance.example.com/api/permission-changes
└── Webhook: Tenant.TenantRemovedEvent
└── URL: https://compliance.example.com/api/data-deletion4. Real-Time Analytics
Analytics Platform:
├── Webhook: Customer.CustomerCreatedEvent
│ └── URL: https://analytics.example.com/api/track/customer-created
├── Webhook: Invoice.InvoiceCreatedEvent
│ └── URL: https://analytics.example.com/api/track/invoice-created
└── Webhook: Asset.AssetUploadedEvent
└── URL: https://analytics.example.com/api/track/file-upload5. Multi-Environment Sync
Environment Synchronization:
├── Production Webhook: Customer.CustomerCreatedEvent
│ └── URL: https://staging.example.com/api/sync/customers
└── Production Webhook: Tenant.TenantCreatedEvent
└── URL: https://test.example.com/api/sync/tenantsFeatures
- Event-driven HTTP notifications
- Tenant-scoped and workspace-scoped webhooks
- Event filtering by domain identifier
- Enable/disable without deletion
- Manual webhook testing
- Custom attributes for metadata
- Automatic retry on failure (via middleware)
- JSON payload format
- Real-time event delivery
Best Practices
Webhook URLs
- Use HTTPS for security
- Implement authentication on receiving endpoint
- Use descriptive, versioned URLs (e.g.,
/api/v1/webhooks/customer-created) - Set up dedicated endpoints for different event types
Event Filtering
- Create specific webhooks for each event type (avoid wildcards)
- Use descriptive attributes to document webhook purpose
- Keep workspace webhooks focused on workspace-specific events
- Use tenant webhooks for system-wide monitoring
Error Handling
- Implement idempotency on receiving endpoint (events may be sent multiple times)
- Return 2xx status codes quickly to avoid timeouts
- Log all webhook deliveries for debugging
- Implement retry logic on receiving side
Security
- Validate webhook signatures (if implemented)
- Use HTTPS endpoints only
- Implement rate limiting on receiving endpoint
- Store webhook URLs securely
Performance
- Process webhook payloads asynchronously
- Respond quickly to webhook requests (< 5 seconds)
- Disable webhooks that consistently fail
- Use attributes to track webhook health metrics
Testing
- Use test endpoint before activating webhooks
- Monitor webhook delivery logs
- Test error scenarios (timeouts, 5xx responses)
- Validate payload format matches expectations
Webhook Middleware
The webhook domain includes middleware that intercepts all commands:
How it Works:
- Command executes and generates events
- Webhook middleware intercepts events
- For each event, middleware checks all active webhooks
- If webhook's
DomainEvtIdentifiermatches event, HTTP POST is sent - Tenant UUID and workspace UUID are validated
Example Flow:
Command: CustomerCommandCreate
↓
Events: [CustomerCreatedEvent]
↓
Webhook Middleware
↓
Active Webhooks: [Webhook1: Customer.CustomerCreatedEvent]
↓
Match Found → HTTP POST to Webhook1.WebhookUrlTroubleshooting
Webhook not triggering
Check:
- Is the webhook active? (Active = true)
- Does
DomainEvtIdentifiermatch exactly? - Does event's tenant match webhook's tenant?
- For workspace webhooks, does event's workspace match?
Debug:
// Check webhook configuration
webhook, _ := GetWebhookReadmodel().GetModel("webhook-uuid")
fmt.Printf("Active: %v\n", webhook.Active)
fmt.Printf("DomainEvtIdentifier: %s\n", webhook.DomainEvtIdentifier)
fmt.Printf("WebhookUrl: %s\n", webhook.WebhookUrl)
// Check if events are being generated
// Look for middleware logsEvents not reaching endpoint
Possible causes:
- Endpoint is unreachable (DNS, network)
- Endpoint returns non-2xx status code
- Request times out
- Firewall blocking requests
Solution:
- Use webhook test endpoint to verify connectivity
- Check endpoint logs for incoming requests
- Verify URL is correct and publicly accessible
- Test with tools like webhook.site or requestbin.com
Duplicate webhook deliveries
Cause: Event sourcing and middleware architecture may send same event multiple times.
Solution:
- Implement idempotency on receiving endpoint
- Use event UUID to deduplicate
- Store processed event UUIDs to prevent reprocessing
Wrong events being sent
Cause: DomainEvtIdentifier doesn't match expected pattern.
Solution:
- Verify exact event name from domain (e.g.,
Customer.CustomerCreatedEvent) - Check capitalization and spelling
- List available domain events using Runtime domain queries
Performance issues
Cause: Webhook endpoint is slow or blocking.
Solution:
- Respond with 200 OK immediately, process asynchronously
- Implement timeout on receiving side (< 5 seconds)
- Use message queue on receiving side for processing
- Consider disabling slow webhooks
Security Considerations
Endpoint Security
- Only use HTTPS endpoints for webhooks
- Implement authentication on receiving endpoint
- Validate webhook signatures (if implemented)
- Rate limit incoming webhook requests
Data Privacy
- Webhooks contain full event data, including sensitive information
- Only send webhooks to trusted endpoints
- Consider filtering sensitive fields before sending
- Implement data retention policies on receiving side
Access Control
- Limit who can create/modify webhooks (use group permissions)
- Audit webhook creation and modifications
- Review webhook URLs regularly
- Delete unused webhooks promptly
Network Security
- Whitelist outbound webhook IPs if possible
- Monitor webhook delivery failures
- Implement circuit breakers for failing webhooks
- Log all webhook attempts for audit
Integration Examples
Slack Integration
// Create webhook for Slack notifications
cmd, _ := comby.NewCommand("Webhook", &command.WebhookCommandAdd{
WebhookUuid: uuid.New().String(),
DomainEvtIdentifier: "Customer.CustomerCreatedEvent",
WebhookUrl: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
Attributes: "integration:slack,channel:#sales",
})
facade.DispatchCommand(ctx, cmd)Receiving Endpoint (Slack Webhook): Slack expects a specific format. You may need an adapter service:
{
"text": "New customer created: {{event.domainEvt.name}}"
}Custom Integration
// Create webhook for custom application
cmd, _ := comby.NewCommand("Webhook", &command.WebhookCommandAdd{
WebhookUuid: uuid.New().String(),
DomainEvtIdentifier: "Invoice.InvoiceCreatedEvent",
WebhookUrl: "https://myapp.example.com/api/webhooks/invoice",
Attributes: "integration:custom-app,environment:production",
})
facade.DispatchCommand(ctx, cmd)Receiving Endpoint:
func WebhookHandler(w http.ResponseWriter, r *http.Request) {
// Parse webhook payload
var event comby.Event
json.NewDecoder(r.Body).Decode(&event)
// Verify tenant matches expected tenant
if event.TenantUuid != expectedTenant {
w.WriteHeader(http.StatusForbidden)
return
}
// Process event asynchronously
go processEvent(event)
// Respond immediately
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"received"}`))
}Commands
- WebhookCommandAdd
- WebhookCommandRemove
- WebhookCommandRemoveAttribute
- WebhookCommandSetAttribute
- WebhookCommandUpdate
WebhookCommandAdd
Domain Command Struct:
type WebhookCommandAdd struct {
WebhookUuid string `json:"webhookUuid"`
DomainEvtIdentifier string `json:"domainEvtIdentifier"`
WebhookUrl string `json:"webhookUrl"`
WorkspaceUuid string `json:"workspaceUuid,omitempty"` // Optional workspace UUID for workspace-scoped webhooks
Attributes string `json:"attributes,omitempty"`
}Domain Command Handling Method:
func (cs *commandHandler) WebhookCommandAdd(ctx context.Context, cmd comby.Command, domainCmd *WebhookCommandAdd) ([]comby.Event, error)WebhookCommandRemove
Domain Command Struct:
type WebhookCommandRemove struct {
WebhookUuid string `json:"webhookUuid"`
}Domain Command Handling Method:
func (cs *commandHandler) WebhookCommandRemove(ctx context.Context, cmd comby.Command, domainCmd *WebhookCommandRemove) ([]comby.Event, error)WebhookCommandRemoveAttribute
Domain Command Struct:
type WebhookCommandRemoveAttribute struct {
WebhookUuid string `json:"webhookUuid"`
Key string `json:"key"`
}Domain Command Handling Method:
func (cs *commandHandler) WebhookCommandRemoveAttribute(ctx context.Context, cmd comby.Command, domainCmd *WebhookCommandRemoveAttribute) ([]comby.Event, error)WebhookCommandSetAttribute
Domain Command Struct:
type WebhookCommandSetAttribute struct {
WebhookUuid string `json:"webhookUuid"`
Key string `json:"key"`
Value any `json:"value"`
}Domain Command Handling Method:
func (cs *commandHandler) WebhookCommandSetAttribute(ctx context.Context, cmd comby.Command, domainCmd *WebhookCommandSetAttribute) ([]comby.Event, error)WebhookCommandUpdate
Domain Command Struct:
type WebhookCommandUpdate struct {
WebhookUuid string `json:"webhookUuid"`
Active bool `json:"active,omitempty"`
DomainEvtIdentifier string `json:"domainEvtIdentifier,omitempty"`
WebhookUrl string `json:"webhookUrl,omitempty"`
Attributes string `json:"attributes,omitempty"`
PatchedFields []string `json:"patchedFields"`
}Domain Command Handling Method:
func (cs *commandHandler) WebhookCommandUpdate(ctx context.Context, cmd comby.Command, domainCmd *WebhookCommandUpdate) ([]comby.Event, error)Queries
Domain Query Structs:
Domain Query Responses:
WebhookQueryList
Domain Query Struct:
type WebhookQueryList struct {
TenantUuid string `json:"tenantUuid"`
Page int64 `json:"page,omitempty"`
PageSize int64 `json:"pageSize,omitempty"`
OrderBy string `json:"orderBy,omitempty"`
Attributes string `json:"attributes,omitempty"`
IncludeHistory bool `json:"includeHistory,omitempty"`
}Domain Query Handling Method:
func (qs *queryService) WebhookQueryList(ctx context.Context, qry comby.Query, domainQry *WebhookQueryList) (*WebhookQueryListResponse, error)WebhookQueryModel
Domain Query Struct:
type WebhookQueryModel struct {
WebhookUuid string `json:"webhookUuid"`
IncludeHistory bool `json:"includeHistory,omitempty"`
}Domain Query Handling Method:
func (qs *queryService) WebhookQueryModel(ctx context.Context, qry comby.Query, domainQry *WebhookQueryModel) (*WebhookQueryItemResponse, error)WebhookQueryListResponse
type WebhookQueryListResponse struct {
Items []*readmodel.WebhookModel `json:"items,omitempty"`
Total int64 `json:"total,omitempty"`
Page int64 `json:"page,omitempty"`
PageSize int64 `json:"pageSize,omitempty"`
}WebhookQueryItemResponse
type WebhookQueryItemResponse struct {
Item *readmodel.WebhookModel `json:"item,omitempty"`
}Events
- WebhookAddedEvent
- WebhookRemovedEvent
- WebhookAttributeRemovedEvent
- WebhookAttributeSetEvent
- WebhookUpdatedEvent
WebhookAddedEvent
Domain Event Struct:
type WebhookAddedEvent struct {
DomainEvtIdentifier string `json:"domainEvtIdentifier"`
WebhookUrl string `json:"webhookUrl"`
WorkspaceUuid string `json:"workspaceUuid,omitempty"` // Optional workspace UUID for workspace-scoped webhooks
Attributes string `json:"attributes,omitempty"`
}Domain Event Handling Method:
func (agg *Webhook) WebhookAddedEvent(ctx context.Context, evt comby.Event, domainEvt *WebhookAddedEvent) (error)WebhookRemovedEvent
Domain Event Struct:
type WebhookRemovedEvent struct {
Reason string `json:"reason,omitempty"`
}Domain Event Handling Method:
func (agg *Webhook) WebhookRemovedEvent(ctx context.Context, evt comby.Event, domainEvt *WebhookRemovedEvent) (error)WebhookAttributeRemovedEvent
Domain Event Struct:
type WebhookAttributeRemovedEvent struct {
Key string `json:"key"`
}Domain Event Handling Method:
func (agg *Webhook) WebhookAttributeRemovedEvent(ctx context.Context, evt comby.Event, domainEvt *WebhookAttributeRemovedEvent) (error)WebhookAttributeSetEvent
Domain Event Struct:
type WebhookAttributeSetEvent struct {
Key string `json:"key"`
Value any `json:"value"`
}Domain Event Handling Method:
func (agg *Webhook) WebhookAttributeSetEvent(ctx context.Context, evt comby.Event, domainEvt *WebhookAttributeSetEvent) (error)WebhookUpdatedEvent
Domain Event Struct:
type WebhookUpdatedEvent struct {
Active bool `json:"active"`
DomainEvtIdentifier string `json:"domainEvtIdentifier"`
WebhookUrl string `json:"webhookUrl"`
Attributes string `json:"attributes,omitempty"`
}Domain Event Handling Method:
func (agg *Webhook) WebhookUpdatedEvent(ctx context.Context, evt comby.Event, domainEvt *WebhookUpdatedEvent) (error)Aggregate
Aggregate Struct:
type Webhook struct {
*comby.BaseAggregate
// WorkspaceUuid - when set, this webhook is for a specific workspace
WorkspaceUuid string
// Value Objects
Active bool
DomainEvtIdentifier string
WebhookUrl string
}Methods
Add
func (agg *Webhook) Add(opts ) (error)Remove
func (agg *Webhook) Remove(opts ) (error)RemoveAttribute
func (agg *Webhook) RemoveAttribute(opts ) (error)SetAttribute
func (agg *Webhook) SetAttribute(opts ) (error)Update
func (agg *Webhook) Update(opts ) (error)Event Handlers
WebhookReadmodel
| Domain Event | Method |
|---|---|
workspaceAggregate.WorkspaceCreatedEvent | WorkspaceCreatedEvent |
workspaceAggregate.WorkspaceRemovedEvent | WorkspaceRemovedEvent |
workspaceAggregate.WorkspaceUpdatedEvent | WorkspaceUpdatedEvent |
aggregate.WebhookAddedEvent | WebhookAddedEvent |
aggregate.WebhookUpdatedEvent | WebhookUpdatedEvent |
aggregate.WebhookRemovedEvent | WebhookRemovedEvent |
aggregate.WebhookAttributeSetEvent | WebhookAttributeSetEvent |
aggregate.WebhookAttributeRemovedEvent | WebhookAttributeRemovedEvent |
tenantAggregate.TenantCreatedEvent | TenantCreatedEvent |
tenantAggregate.TenantRemovedEvent | TenantRemovedEvent |
tenantAggregate.TenantUpdatedEvent | TenantUpdatedEvent |