Events and Webhooks Reference

Parent Document: API_Reference.md Implementation Folder: app/src/Task/ Codeunit: Cloud Event Message Events (Codeunit 65346)


Overview

The Cloud Events extension provides native Business Central External Business Events that enable external systems to receive webhook notifications when cloud event messages complete or fail. This allows for event-driven architectures where external systems are notified immediately rather than polling for status.

Key Features:


Event Architecture

External Business Events

Business Central's External Business Events allow external systems to subscribe to events and receive HTTP POST notifications when events occur. The Cloud Events extension exposes two external business events:

  1. CloudEventMessageCompleted: Raised when a message processes successfully
  2. CloudEventMessageFailed: Raised when a message processing fails

Event Category

All Cloud Events webhook notifications are categorized under:

This category can be used to filter and organize event subscriptions in Business Central.

Minimal Payload Pattern

Webhook notifications intentionally send minimal data to:

After receiving a webhook notification, subscribers call the Cloud Event Data API using the MessageId to retrieve the full response.


Event: CloudEventMessageCompleted

Purpose: Notifies external systems when a cloud event message has completed processing successfully.

Event Name: CloudEventMessageCompleted Event Display Name: Cloud Event Message Completed Event Category: Origo Cloud Event Raised By: Codeunit 65313 Cloud Event Message Task

When This Event is Raised

The event is raised after a cloud event message has been successfully processed:

  1. Message is submitted to Queue API or Task API
  2. Message processing begins (via background task or synchronously)
  3. Implementation executes business logic successfully
  4. Response data is stored in Cloud Event Message table
  5. Event is raised with MessageId, MessageType, and completion timestamp
  6. Webhook notification is sent to all subscribers
  7. Subscribers receive notification and can fetch response data

Webhook Payload

{
  "MessageId": "a8f5f167-8f2c-4a42-9b3e-5c6c7d8e9f0a",
  "MessageType": "Customer.CreditLimit.Get",
  "ResponseContentLink": "/api/origo/cloudEvent/v1.0/responses(a8f5f167-8f2c-4a42-9b3e-5c6c7d8e9f0a)/data",
  "Timestamp": "2026-03-08T14:30:22Z"
}

Payload Fields

FieldTypeDescription
MessageIdGuidUnique identifier for the message. Use this to call GET /cloudEventData(MessageId) to retrieve full response.
MessageTypeText[250]The type of message that completed (e.g., "Customer.CreditLimit.Get", "Data.Records.Get"). Can be used for routing or filtering.
ResponseContentLinkText[250]Direct API link to download response data. Use this URL to retrieve the full response without constructing the API path manually.
TimestampDateTimeWhen the message completed processing (ISO 8601 format).

Retrieving Full Response Data

After receiving the webhook notification, call the Cloud Event Data API to retrieve the full response:

Request:

GET /api/origo/cloudEvent/v1.0/responses('{message-id}')
Authorization: Bearer {token}

Response:

{
  "id": "a8f5f167-8f2c-4a42-9b3e-5c6c7d8e9f0a",
  "data": "... full response data as base64 or JSON ..."
}

Example Integration Flow

sequenceDiagram
    participant External as External System
    participant BCQueue as BC Queue API
    participant BCTask as BC Task Processor
    participant Webhook as Webhook Endpoint
    participant BCData as BC Data API

    External->>BCQueue: POST /cloudEventQueue (message)
    BCQueue-->>External: 202 Accepted (MessageId)
    
    BCTask->>BCTask: Process message
    BCTask->>BCTask: Store response data
    BCTask->>Webhook: POST webhook (MessageId, Type, Time)
    
    Webhook->>BCData: GET /cloudEventData(MessageId)
    BCData-->>Webhook: Response data
    Webhook->>Webhook: Process response

Use Cases


Event: CloudEventMessageFailed

Purpose: Notifies external systems when a cloud event message processing has failed.

Event Name: CloudEventMessageFailed Event Display Name: Cloud Event Message Failed Event Category: Origo Cloud Event Raised By: Codeunit 65312 Cloud Event Message Error

When This Event is Raised

The event is raised after a cloud event message processing has failed:

  1. Message is submitted to Queue API or Task API
  2. Message processing begins (via background task or synchronously)
  3. Implementation encounters an error or validation fails
  4. Error details are captured and stored in Cloud Event Message table
  5. Event is raised with MessageId, MessageType, and failure timestamp
  6. Webhook notification is sent to all subscribers
  7. Subscribers receive notification and can fetch error details

Webhook Payload

{
  "MessageId": "b9f6f267-9f3d-5b52-0c4f-6d7d8e9f1b1b",
  "MessageType": "Data.Records.Set",
  "ResponseContentLink": "/api/origo/cloudEvent/v1.0/responses(b9f6f267-9f3d-5b52-0c4f-6d7d8e9f1b1b)/data",
  "Timestamp": "2026-03-08T14:35:18Z"
}

Payload Fields

FieldTypeDescription
MessageIdGuidUnique identifier for the message. Use this to call GET /cloudEventQueue(MessageId) to retrieve error details.
MessageTypeText[250]The type of message that failed (e.g., "Data.Records.Set", "Sales.Document.Release").
ResponseContentLinkText[250]Direct API link to download error details. Use this URL to retrieve the error response without constructing the API path manually.
TimestampDateTimeWhen the message failed processing (ISO 8601 format).

Retrieving Error Details

After receiving the webhook notification, call the Cloud Event Queue API to retrieve error details:

Request:

GET /api/origo/cloudEvent/v1.0/queues('{message-id}')
Authorization: Bearer {token}

Response:

{
  "id": "b9f6f267-9f3d-5b52-0c4f-6d7d8e9f1b1b",
  "type": "Data.Records.Set",
  "specversion": "1.0",
  "source": "MyIntegrationApp v1.0",
  "time": "2026-03-08T14:35:15Z",
  "datacontenttype": "text/json",
  "data": "{
    \"error\": \"Record not found\",
    \"detailedMessage\": \"Table: Customer, SystemId: {guid}\",
    \"stackTrace\": \"...\",
    \"callStack\": \"...\"
  }"
}

Error Response Format

Error responses are stored in JSON format with the following fields:

Example Integration Flow

sequenceDiagram
    participant External as External System
    participant BCQueue as BC Queue API
    participant BCTask as BC Task Processor
    participant Webhook as Webhook Endpoint
    participant BCQueue2 as BC Queue API

    External->>BCQueue: POST /cloudEventQueue (message)
    BCQueue-->>External: 202 Accepted (MessageId)
    
    BCTask->>BCTask: Process message
    BCTask->>BCTask: ERROR OCCURS
    BCTask->>BCTask: Store error data
    BCTask->>Webhook: POST webhook (MessageId, Type, Time)
    
    Webhook->>BCQueue2: GET /cloudEventQueue(MessageId)
    BCQueue2-->>Webhook: Error details
    Webhook->>Webhook: Log error & alert

Use Cases


Integration Events

In addition to External Business Events for webhooks, the Cloud Events extension provides Integration Events that allow other Business Central extensions to react to message lifecycle events.

OnBeforeCloudEventMessageProcessing

Purpose: Raised before a cloud event message starts processing.

Event Type: IntegrationEvent Visibility: Internal Raised By: Codeunit 65313 Cloud Event Message Task

Signature:

[IntegrationEvent(false, false)]
internal procedure OnBeforeCloudEventMessageProcessing(var CloudEventMessage: Record "Cloud Event Message")

Parameters:

Use Cases:

OnAfterCloudEventMessageCompleted

Purpose: Raised after a cloud event message completes successfully.

Event Type: IntegrationEvent Visibility: Internal Raised By: Codeunit 65313 Cloud Event Message Task

Signature:

[IntegrationEvent(false, false)]
internal procedure OnAfterCloudEventMessageCompleted(var CloudEventMessage: Record "Cloud Event Message")

Parameters:

Use Cases:

OnAfterCloudEventMessageFailed

Purpose: Raised after a cloud event message processing fails.

Event Type: IntegrationEvent Visibility: Internal Raised By: Codeunit 65312 Cloud Event Message Error

Signature:

[IntegrationEvent(false, false)]
internal procedure OnAfterCloudEventMessageFailed(var CloudEventMessage: Record "Cloud Event Message"; ErrorText: Text)

Parameters:

Use Cases:


Setting Up Webhook Subscriptions

Prerequisites

  1. External Webhook Endpoint: You need an HTTPS endpoint that can receive POST requests
  2. Endpoint Requirements:

Configuration Steps

Step 1: Navigate to Event Subscriptions

  1. Open Business Central web client
  2. Search for "Event Subscriptions"
  3. Open the Event Subscriptions page

Step 2: Create New Subscription

  1. Click New
  2. Fill in the following fields:
FieldValueDescription
Subscriber ID(Auto-generated)Unique identifier for the subscription
Event NameCloudEventMessageCompleted or CloudEventMessageFailedChoose which event to subscribe to
Company NameYour company nameCompany context for the event
Event CategoryOrigo Cloud EventFilter to Cloud Events category
Endpoint URLhttps://your-domain.com/webhook/bc-cloud-eventsYour webhook endpoint URL
Authentication(Select method)How to authenticate to your endpoint

Step 3: Configure Authentication

Choose authentication method:

Option 1: OAuth 2.0 (Recommended)

Option 2: Basic Authentication

Option 3: API Key

Option 4: None

Step 4: Test Subscription

  1. Use the "Test Subscription" action to send a test event
  2. Verify your endpoint receives the test payload
  3. Check the "Last Delivery Status" field for success/failure

Step 5: Activate Subscription

  1. Set "Enabled" field to Yes
  2. The subscription is now active and will receive events

Webhook Endpoint Best Practices

1. Idempotency

Your endpoint should handle duplicate notifications gracefully:

// Example: Node.js Express endpoint
app.post('/webhook/bc-cloud-events', async (req, res) => {
  const { MessageId, MessageType, ResponseContentLink, Timestamp } = req.body;
  
  // Check if we've already processed this message
  const exists = await db.checkMessageProcessed(MessageId);
  if (exists) {
    console.log(`Duplicate notification for ${MessageId}, ignoring`);
    return res.status(200).send('OK'); // Still return 200 to prevent retries
  }
  
  // Mark as processed before fetching data
  await db.markMessageProcessing(MessageId);
  
  // Fetch full response data from BC using the provided link
  const response = await fetchCloudEventData(ResponseContentLink);
  
  // Process the response
  await processResponse(response, MessageType);
  
  // Mark as completed
  await db.markMessageCompleted(MessageId);
  
  res.status(200).send('OK');
});

2. Asynchronous Processing

Respond to webhook quickly and process data asynchronously:

app.post('/webhook/bc-cloud-events', async (req, res) => {
  const { MessageId, MessageType, ResponseContentLink, Timestamp } = req.body;
  
  // Immediately queue for background processing
  await queue.enqueue({
    messageId: MessageId,
    messageType: MessageType,
    responseContentLink: ResponseContentLink,
    timestamp: Timestamp
  });
  
  // Respond immediately
  res.status(200).send('OK');
});

// Background worker processes the queue
backgroundWorker.on('job', async (job) => {
  const response = await fetchCloudEventData(job.responseContentLink);
  await processResponse(response, job.messageType);
});

3. Error Handling

Implement proper error handling and logging:

app.post('/webhook/bc-cloud-events', async (req, res) => {
  try {
    const { MessageId, MessageType, ResponseContentLink, Timestamp } = req.body;
    
    // Validate payload
    if (!MessageId || !MessageType || !ResponseContentLink || !Timestamp) {
      console.error('Invalid payload received', req.body);
      return res.status(400).send('Invalid payload');
    }
    
    // Queue for processing
    await queue.enqueue({
      messageId: MessageId,
      messageType: MessageType,
      responseContentLink: ResponseContentLink,
      timestamp: Timestamp
    });
    
    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook processing error:', error);
    // Return 4xx for client errors (don't retry)
    // Return 5xx for server errors (BC will retry)
    res.status(500).send('Internal Server Error');
  }
});

4. Retry Handling

Handle retries with exponential backoff when fetching data from BC:

async function fetchCloudEventData(messageId, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await bcApi.get(`/cloudEventData(${messageId})`);
      return response.data;
    } catch (error) {
      if (attempt === maxRetries) throw error;
      
      // Exponential backoff: 1s, 2s, 4s
      const delay = Math.pow(2, attempt - 1) * 1000;
      await sleep(delay);
    }
  }
}

5. Monitoring and Alerting

Implement monitoring for webhook delivery failures:

app.post('/webhook/bc-cloud-events', async (req, res) => {
  const startTime = Date.now();
  
  try {
    const { MessageId, MessageType, Timestamp } = req.body;
    
    await queue.enqueue({
      messageId: MessageId,
      messageType: MessageType,
      timestamp: Timestamp
    });
    
    // Track success metrics
    metrics.webhookReceived(MessageType);
    metrics.webhookLatency(Date.now() - startTime);
    
    res.status(200).send('OK');
  } catch (error) {
    // Track failure metrics
    metrics.webhookFailed(error);
    
    // Alert on critical failures
    if (shouldAlert(error)) {
      alerting.sendAlert('Webhook processing failure', error);
    }
    
    res.status(500).send('Internal Server Error');
  }
});

Testing Webhooks

Test Message Submission

Submit a test message to trigger webhook notifications:

# Submit test message to Queue API
curl -X POST "https://your-bc-instance/api/origo/cloudEvent/v1.0/queues" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "specversion": "1.0",
    "type": "Help.Tables.Get",
    "source": "Webhook Test v1.0"
  }'

Monitor Event Delivery

  1. Open Event Subscriptions page in Business Central
  2. Find your subscription
  3. Check the following fields:

Troubleshooting

Webhook Not Receiving Events

  1. Check Subscription Status: Ensure subscription is Enabled
  2. Verify Event Name: Confirm you're subscribed to correct event (CloudEventMessageCompleted or CloudEventMessageFailed)
  3. Check Endpoint URL: Verify URL is correct and accessible
  4. Test Connectivity: Use "Test Subscription" action in Event Subscriptions
  5. Review Firewall Rules: Ensure BC can reach your endpoint
  6. Check Authentication: Verify credentials are correct

Delivery Failures

  1. Check Endpoint Response Time: Must respond within timeout (default 30s)
  2. Verify HTTPS: Endpoint must use HTTPS, not HTTP
  3. Check Status Code: Endpoint must return 2xx status code
  4. Review Error Logs: Check "Last Delivery Error" field in Event Subscriptions
  5. Test Manually: Call your endpoint directly with sample payload

Duplicate Notifications

Business Central may send duplicate notifications in certain scenarios:

Solution: Implement idempotency in your webhook endpoint (see Best Practices above)


Security Considerations

1. Authentication

Always use authentication for webhook endpoints:

2. HTTPS Only

Business Central enforces HTTPS for webhook endpoints. HTTP endpoints are not supported for security reasons.

3. Data Privacy

Webhook notifications contain minimal data (MessageId, MessageType, Timestamp) to:

Always fetch full data via authenticated API calls after receiving webhook notification.

4. Endpoint Security

Protect your webhook endpoint:

5. Error Information

Error details (stackTrace, callStack) in failed message responses may contain:

Recommendation: Restrict access to error details to authorized personnel only.


Performance Considerations

1. Webhook Response Time

Respond to webhooks within 30 seconds (default timeout):

2. Data Fetching

When fetching response data after webhook notification:

3. Message Volume

For high-volume scenarios:

4. Monitoring

Track key metrics:


Code Reference

External Business Events

Codeunit: 65346 Cloud Event Message Events

/// Raised when a cloud event message processing completes successfully
[ExternalBusinessEvent('CloudEventMessageCompleted', 'Cloud Event Message Completed', 
  'A cloud event message has completed successfully.', EventCategory::"Origo Cloud Event")]
procedure OnCloudEventMessageCompleted(MessageId: Guid; MessageType: Text[250]; Timestamp: DateTime)

/// Raised when a cloud event message processing fails
[ExternalBusinessEvent('CloudEventMessageFailed', 'Cloud Event Message Failed', 
  'A cloud event message has failed processing.', EventCategory::"Origo Cloud Event")]
procedure OnCloudEventMessageFailed(MessageId: Guid; MessageType: Text[250]; Timestamp: DateTime)

Integration Events

/// Raised before a cloud event message starts processing
[IntegrationEvent(false, false)]
internal procedure OnBeforeCloudEventMessageProcessing(var CloudEventMessage: Record "Cloud Event Message")

/// Raised after a cloud event message completes successfully
[IntegrationEvent(false, false)]
internal procedure OnAfterCloudEventMessageCompleted(var CloudEventMessage: Record "Cloud Event Message")

/// Raised after a cloud event message processing fails
[IntegrationEvent(false, false)]
internal procedure OnAfterCloudEventMessageFailed(var CloudEventMessage: Record "Cloud Event Message"; ErrorText: Text)

Event Category Extension

EnumExtension: 65300 Cloud Event Category

enumextension 65300 "Cloud Event Category" extends EventCategory
{
    value(65300; "Origo Cloud Event")
    {
        Caption = 'Origo Cloud Event';
    }
}

Related Documentation


© 2024 Origo. All rights reserved.


© Origo – Cloud Events Base Extension