Webhooks
Overview
The webhook system allows clients to configure a single HTTP callback that will be triggered when certain events occur in the BKBN platform. Each client can configure one webhook endpoint with authentication settings. This enables real-time notifications and integration with external systems.
API Endpoints
Authentication
All webhook endpoints require JWT authentication. Include your token in the Authorization header:
Authorization: Bearer <your-jwt-token>
Webhook Management
Get Current Webhook Configuration
GET /webhook
Returns the current webhook configuration for the authenticated client.
Response:
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"url": "https://your-domain.com/webhook",
"enabled": true,
"authType": "BEARER_TOKEN",
"hasAuthSecret": true,
"createdAt": "2023-01-15T10:30:00Z",
"updatedAt": "2023-01-15T10:30:00Z",
"lastTriggeredAt": "2023-01-15T12:45:00Z"
}
Note: The authSecret
field is never returned in responses for security reasons. The hasAuthSecret
field indicates whether a secret is configured.
Create/Update Webhook Configuration
POST /webhook
PUT /webhook
Creates or updates a webhook configuration.
Request Body:
{
"url": "https://your-domain.com/webhook",
"enabled": true,
"authType": "BEARER_TOKEN",
"authSecret": "your-secret-token",
"authHeaderName": "X-Custom-Auth",
"authParameterName": "auth_token"
}
Security Notes:
- The
authSecret
field is only used for input and is never returned in responses - When updating a webhook, omit
authSecret
to keep the existing secret unchanged - Include
authSecret
only when you want to set a new secret value
Delete Webhook Configuration
DELETE /webhook
Permanently removes the webhook configuration.
Enable/Disable Webhook
PATCH /webhook/enabled?enabled=true
Enables or disables the webhook without removing the configuration.
Get Webhook Trigger History
GET /webhook/history
Returns the last 10 webhook trigger attempts with details about success/failure.
Response:
[
{
"id": "456e7890-e89b-12d3-a456-426614174001",
"triggeredAt": "2023-01-15T12:45:00Z",
"payload": {
"event": "VISUALS_READY",
"orderId": "12345",
"assignmentId": "12345-A",
"visualType": "POST",
"product": "GROUND_PHOTO",
"realEstatePropertyId": "RE-123",
"timestamp": "2023-01-15T12:45:00Z"
},
"headers": {
"X-Event-Type": "VISUALS_READY"
},
"statusCode": 200,
"success": true
},
{
"id": "789e1234-e89b-12d3-a456-426614174002",
"triggeredAt": "2023-01-15T11:30:00Z",
"payload": {
"event": "VISUALS_READY",
"orderId": "12346",
"assignmentId": "12346-A",
"visualType": "DOCUMENT",
"product": "FLOOR_PLAN"
},
"statusCode": 500,
"responseBody": "{\"error\": \"Database connection failed\"}",
"errorMessage": "Webhook call failed with status 500 Internal Server Error",
"success": false
}
]
Authentication Types
NONE
No authentication is applied to webhook requests.
{
"authType": "NONE"
}
HEADER
Adds a custom header with the secret value.
{
"authType": "HEADER",
"authSecret": "your-secret",
"authHeaderName": "X-API-Key"
}
Result: X-API-Key: your-secret
QUERY_PARAMETER
Adds the secret as a query parameter.
{
"authType": "QUERY_PARAMETER",
"authSecret": "your-secret",
"authParameterName": "api_key"
}
Result: ?api_key=your-secret
BEARER_TOKEN
Adds an Authorization header with Bearer token.
{
"authType": "BEARER_TOKEN",
"authSecret": "your-token"
}
Result: Authorization: Bearer your-token
API_KEY_HEADER
Adds an X-API-Key header with the secret.
{
"authType": "API_KEY_HEADER",
"authSecret": "your-api-key"
}
Result: X-API-Key: your-api-key
Webhook Events
Visuals Ready
Triggered when materials (visuals, documents, or other deliverables) are ready for download.
{
"event": "VISUALS_READY",
"timestamp": "2023-01-15T12:45:00Z",
"orderId": "12345",
"assignmentId": "12345-A",
"visualType": "POST",
"product": "GROUND_PHOTO",
"realEstatePropertyId": "RE-123"
}
When is the VISUALS_READY
event triggered?
VISUALS_READY
event triggered?The event is triggered at different points in the order flow, depending on the product and workflow scenario. For the full scenario and product mapping, see Visuals Ready Event Scenarios.

Payload Fields:
event
: "VISUALS_READY"timestamp
: ISO 8601 timestamp when the webhook was triggeredorderId
: Unique identifier of the orderassignmentId
: Unique identifier of the assignment within the ordervisualType
: Type of visual/resource that's ready (POST, POST_WEB, DOCUMENT, RAW)product
: Type of output/deliverable that's ready (e.g., GROUND_PHOTO, FLOOR_PLAN)realEstatePropertyId
: Optional identifier of the real estate property (if applicable)
Next Steps:
When you receive this webhook, you should call the Materials API to retrieve the actual materials:
GET /materials/{orderId}/{assignmentId}?product={product}&visualType={visualType}
For detailed information about requesting and processing materials, see the Materials API Documentation.
Webhook Headers
All webhook requests include the following headers:
Content-Type: application/json
X-Event-Type: <event-type>
- The type of event that triggered the webhook- Additional headers specific to the event (e.g.,
X-Order-ID
,X-User-ID
)
Response Handling
Your webhook endpoint should:
- Respond quickly - Return a response within 30 seconds
- Return success status - HTTP 200 or 201 indicates successful processing
- Handle retries - Be idempotent as webhooks may be retried
- Validate the payload - Verify the request is legitimate
Expected Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"status": "success",
"message": "Webhook processed successfully"
}
Error Handling
If your webhook endpoint returns an error status (4xx or 5xx), the system will:
- Log the error with response details
- Not retry the webhook call automatically
- Update the
lastTriggeredAt
timestamp regardless of success/failure
Integration Examples
Node.js/Express
app.post('/webhook', (req, res) => {
const { event, orderId, assignmentId, product, visualType } = req.body;
console.log(`Received webhook: ${event} for order ${orderId}, assignment ${assignmentId}`);
if (event === 'VISUALS_READY') {
// Request materials using the Materials API
requestMaterials(orderId, assignmentId, product, visualType);
}
res.json({ status: 'success' });
});
async function requestMaterials(orderId, assignmentId, product, visualType) {
const response = await fetch(
`/materials/${orderId}/${assignmentId}?product=${product}&visualType=${visualType}`,
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
if (response.ok) {
const materials = await response.json();
console.log('Materials received:', materials);
// Process the materials
}
}
Python/Flask
@app.route('/webhook', methods=['POST'])
def handle_webhook():
data = request.get_json()
event = data.get('event')
order_id = data.get('orderId')
assignment_id = data.get('assignmentId')
product = data.get('product')
visual_type = data.get('visualType')
print(f"Received webhook: {event} for order {order_id}, assignment {assignment_id}")
if event == 'VISUALS_READY':
// Request materials using the Materials API
request_materials(order_id, assignment_id, product, visual_type)
return {'status': 'success'}
def request_materials(order_id, assignment_id, product, visual_type):
response = requests.get(
f'/materials/{order_id}/{assignment_id}',
params={
'product': product,
'visualType': visual_type
},
headers={
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
)
if response.status_code == 200:
materials = response.json()
print('Materials received:', materials)
// Process the materials
PHP
<?php
$input = json_decode(file_get_contents('php://input'), true);
$event = $input['event'];
$orderId = $input['orderId'];
$assignmentId = $input['assignmentId'];
$product = $input['product'];
$visualType = $input['visualType'];
error_log("Received webhook: $event for order $orderId, assignment $assignmentId");
if ($event === 'VISUALS_READY') {
// Request materials using the Materials API
requestMaterials($orderId, $assignmentId, $product, $visualType);
}
http_response_code(200);
echo json_encode(['status' => 'success']);
function requestMaterials($orderId, $assignmentId, $product, $visualType) {
$url = "/materials/{$orderId}/{$assignmentId}?product={$product}&visualType={$visualType}";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer {$token}",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$materials = json_decode($response, true);
error_log('Materials received: ' . json_encode($materials));
// Process the materials
}
}
?>
Testing
You can test your webhook integration using services like:
- webhook.site - Free webhook testing
- ngrok - Local development tunneling
- httpbin.org - HTTP request testing
Security Considerations
- HTTPS Only - Always use HTTPS URLs for production webhooks
- Authentication - Use strong secrets for webhook authentication
- Secret Management - Secrets are never returned in API responses for security
- Validation - Validate webhook payloads in your endpoint
- Rate Limiting - Implement rate limiting on your webhook endpoints
- Logging - Log webhook requests for debugging and monitoring (but never log secrets)
Trigger History
The system automatically tracks the last 10 webhook trigger attempts for debugging and monitoring purposes.
What's Tracked
- Timestamp - When the webhook was triggered
- Payload - The data sent to the webhook
- Headers - Additional headers sent with the request
- Status Code - HTTP response code (for both success and failure)
- Response Body - Response content (limited to 4KB, only stored on failure)
- Error Message - Error details (limited to 1KB, only stored on failure)
- Success Flag - Whether the webhook call succeeded
Automatic Cleanup
- Only the last 10 trigger attempts are kept per webhook
- Older records are automatically deleted when new triggers occur
- History is deleted when the webhook is deleted
Accessing History
Use the GET /webhook/history
endpoint to retrieve trigger history for debugging webhook issues.
Troubleshooting
Common Issues
-
Webhook not triggering
- Check if the webhook is enabled
- Verify the URL is accessible
- Check the authentication configuration
-
Authentication failures
- Verify the authentication type and secret
- Check header/parameter names for custom auth
-
Timeout errors
- Ensure your endpoint responds within 30 seconds
- Optimize webhook processing logic
Debug Information
Check the webhook configuration and last triggered timestamp:
GET /webhook
Look for error logs in the service logs for debugging webhook call failures.
Updated 1 day ago