Azure / Azure/data-api-builder
[Enh]: Support Web Hooks
- Dominant language
- C#
- Stars
- 1.5k
- Forks
- 370
- Avg merge
- 3d 17h
- Merged PRs (30d)
- 8
Description
## What?
Support web hooks that trigger before and after CRUD operations on an entity.
* Allows an external service to enforce custom business rules.
* Allows centralized validation across multiple APIs or services.
* Enables auditing and compliance logging of data access and mutations.
* Enables integration with messaging or event systems.
* Enables cache invalidation or search index updates after data changes.
* Enables security checks such as fraud detection or policy enforcement.
* Enables observability and telemetry for downstream monitoring systems.
> [!NOTE]
> This feature supports any HTTP webhook endpoint, including services that publish events to external systems or HTTP-triggered Azure Functions.
## Process flow
```mermaid
sequenceDiagram
participant Client
participant DAB
participant Before as Before Hook
participant DB as Database
participant After as After Hook
Client->>DAB: Request
DAB->>Before: POST
alt stop
Before-->>DAB: stop
DAB-->>Client: Error response
else non-2xx
Before-->>DAB: non-2xx
DAB-->>Client: Error response
else 2xx
Before-->>DAB: Continue
DAB->>DB: CRUD
DB-->>DAB: Result
DAB-->>Client: Response
DAB-->>+After: POST (async)
end
```
## Before
### Outbound payload (to the webhook)
```json
{
"action": "create | read | update | delete | execute",
"entity": "Book",
"timestamp": "2026-03-14T18:22:11Z",
"correlationId": "7b9c5c3e-21d3-4c64-a9e2-4e2e4b0f4f7c",
"source": "rest | graphql | mcp",
"userRole": "authenticated",
"requestData": {
"id": 10,
"title": "Dune",
"author": "Frank Herbert"
}
}
```
| Field | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `action` | Operation that triggered the hook: `create`, `read`, `update`, `delete`, or `execute`. |
| `entity` | Entity name defined in the DAB configuration. |
| `timestamp` | UTC time when the event occurred. |
| `correlationId` | Unique identifier used to correlate the webhook with the original API request. |
| `source` | API surface that triggered the operation: `rest`, `graphql`, or `mcp`. |
| `userRole` | Role assigned to the caller after DAB authorization. |
| `requestData` | Data submitted in the API request that triggered the operation. For `read`, this may contain query parameters or filter information rather than entity data. |
### Inbound payload (from the webhook)
> [!IMPORTANT]
> A webhook may return HTTP status `200` with no payload. In this case, the request proceeds unchanged.
```json
{
"correlationId": "7b9c5c3e-21d3-4c64-a9e2-4e2e4b0f4f7c",
"updateRequestData": {
"title": "New value"
}
}
```
| Field | Description |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `correlationId` | Identifier matching the original webhook request so DAB can associate the response with the correct operation. Generated from the OpenTelemetry trace or span context. |
| `updateRequestData` | Optional object containing field changes the webhook wants to apply before the operation continues. |
The before-hook can return updateRequestData to mutate the request.
1. Any values in `updateRequestData` not in the request are ignored.
2. Any fields, including the primary key fields can be modified.
3. Request substitutions occur before mutation, so authentication is not impacted.
#### Abort example
```json
{
"correlationId": "7b9c5c3e-21d3-4c64-a9e2-4e2e4b0f4f7c",
"stop": {
"reason": "error message"
}
}
```
| Field | Description |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `correlationId` | Identifier matching the original webhook request so DAB can associate the response with the correct operation. Generated from the OpenTelemetry trace or span context. |
| `stop` | Indicates the operation should not continue. |
| `stop.reason` | Human readable explanation for why the request should be rejected. Returned to the client as the error message. |
### Automatic outbound headers
```
x-dab-action: create
x-dab-entity: Book
x-dab-source: rest
x-dab-correlation-id: 7b9c5c3e-21d3-4c64-a9e2-4e2e4b0f4f7c
```
> [!NOTE]
> This is valuable for programmability, but also for telemetry and logging.
## After
### Outbound payload (to the webhook)
```json
{
"action": "create | read | update | delete | execute",
"entity": "Book",
"timestamp": "2026-03-14T18:22:11Z",
"correlationId": "7b9c5c3e-21d3-4c64-a9e2-4e2e4b0f4f7c",
"source": "rest | graphql | mcp",
"userRole": "authenticated",
"result": "success | error",
"responseData": {
"id": 10,
"title": "Dune",
"author": "Frank Herbert"
}
}
```
| Field | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `action` | Operation that triggered the hook: `create`, `read`, `update`, `delete`, or `execute`. |
| `entity` | Entity name defined in the DAB configuration. |
| `timestamp` | UTC time when the event occurred. |
| `correlationId` | Identifier used to correlate the webhook with the originating API request. Generated from the OpenTelemetry trace or span context. |
| `source` | API surface that triggered the operation: `rest`, `graphql`, or `mcp`. |
| `userRole` | Role assigned to the caller after DAB authorization. |
| `result` | Outcome of the database operation: `success` or `error`. |
| `responseData` | Record returned from the operation or the final persisted state of the entity. For `read`, this may contain query result metadata or returned records. |
### Inbound payload (from the webhook)
After is fire and forget, so there is no inbound payload.
### Automatic outbound headers
```
x-dab-action: create
x-dab-entity: Book
x-dab-source: rest
x-dab-correlation-id: 7b9c5c3e-21d3-4c64-a9e2-4e2e4b0f4f7c
```
> [!NOTE]
> These headers are identical to those sent for the `before` webhook.
## Configuration
```json
{
"entities": {
"Book": {
"webhooks": {
"before": {
"enabled": true,
"url": "https://example.com/hooks/before",
"actions": ["*"],
"timeout-seconds": 5,
"abort-on-failure": true
},
"after": {
"enabled": true,
"url": "https://example.com/hooks/after",
"actions": ["delete"]
},
"headers": {
"x-api-key": "@env('WEBHOOK_SECRET')"
}
}
}
}
}
```
> [!IMPORTANT]
> This feature supports `read`, but it is not recommended due to potential performance and event rate impact.
| Property | Default | Description |
| ------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `before.enabled` | `false` | Enables the `before` webhook. Disabled by default to avoid unexpected request interception. |
| `before.url` | `""` | Endpoint DAB calls before executing the operation. Required when `before.enabled` is `true`. |
| `before.actions` | `[]` | Operations that trigger the `before` webhook: `create`, `read`, `update`, `delete`, `execute`. Empty means no actions are enabled. |
| `before.timeout-seconds` | `5` | Maximum time DAB waits for the webhook response before continuing or failing the request. |
| `before.abort-on-failure` | `true` | Determines behavior when the webhook cannot be reached or returns a non-2xx response. Does not apply when the webhook explicitly returns `stop`. |
| `after.enabled` | `false` | Enables the `after` webhook, which runs asynchronously after the operation completes. |
| `after.url` | `""` | Endpoint DAB calls after the operation completes. Required when `after.enabled` is `true`. |
| `after.actions` | `[]` | Operations that trigger the `after` webhook: `create`, `read`, `update`, `delete`, `execute`. Empty means no actions are enabled. |
| `headers` | `{}` | Optional HTTP headers included with webhook requests. Applied to both `before` and `after` hooks. |
### abort-on-failure
| Failure scenario | Example | Should abort? |
| -------------------------- | ---------------------------------------- | ------------- |
| **Connection refused** | Webhook endpoint is down | Yes |
| **DNS resolution failure** | URL is misconfigured | Yes |
| **Timeout exceeded** | Endpoint hangs beyond `timeout-seconds` | Yes |
| **HTTP 5xx** | Webhook server error | Yes |
| **HTTP 4xx** | Webhook explicitly rejects the operation | Yes |
| **HTTP 2xx** | Success | No |
> [!NOTE]
> The contract is simple: anything other than a 2xx response, or an inability to get one, is treated as a failure.
> [!IMPORTANT]
> The `abort-on-failure` setting is independent of a `stop` instruction returned in a `before` webhook response. A `stop` always rejects the request regardless of the `abort-on-failure` value.
## Command Line
```
dab update Book --webhooks.before.enabled true
dab update Book --webhooks.before.url "https://example.com/hooks/before"
dab update Book --webhooks.before.actions "create,read,update,delete,execute"
dab update Book --webhooks.before.timeout-seconds 5
dab update Book --webhooks.before.abort-on-failure true
dab update Book --webhooks.after.enabled true
dab update Book --webhooks.after.url "https://example.com/hooks/after"
dab update Book --webhooks.after.actions "create,read,update,delete,execute"
dab update Book --webhooks.headers "x-api-key:@env('WEBHOOK_SECRET')"
```
Contributor guide
Assessment
This issue has not been assessed yet.