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?

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.

Visuals Ready Event Flow

Payload Fields:

  • event: "VISUALS_READY"
  • timestamp: ISO 8601 timestamp when the webhook was triggered
  • orderId: Unique identifier of the order
  • assignmentId: Unique identifier of the assignment within the order
  • visualType: 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:

  1. Respond quickly - Return a response within 30 seconds
  2. Return success status - HTTP 200 or 201 indicates successful processing
  3. Handle retries - Be idempotent as webhooks may be retried
  4. 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:

Security Considerations

  1. HTTPS Only - Always use HTTPS URLs for production webhooks
  2. Authentication - Use strong secrets for webhook authentication
  3. Secret Management - Secrets are never returned in API responses for security
  4. Validation - Validate webhook payloads in your endpoint
  5. Rate Limiting - Implement rate limiting on your webhook endpoints
  6. 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

  1. Webhook not triggering

    • Check if the webhook is enabled
    • Verify the URL is accessible
    • Check the authentication configuration
  2. Authentication failures

    • Verify the authentication type and secret
    • Check header/parameter names for custom auth
  3. 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.