Verentis

Webhook receivers

Webhook receivers

Let an external system push changes into your app. A WebhookReceiver manifest provisions the endpoint, hands your app its URL and token, and runs your register script when the app is installed.

Apps that mirror data from an external system (a CRM, a farm-management platform) need to know when that data changes. Instead of asking users to create a webhook in the workspace settings, an app declares a kind: WebhookReceiver manifest and installs it with spec.install. When the app is installed, the platform:

  1. creates a managed inbound endpoint for the workspace. It accepts the provider's raw JSON, so no Verentis envelope or signature is needed.
  2. writes the endpoint URL and token into your app's settings.
  3. runs your register script, which subscribes the external system to that URL.

When the manifest is removed, for example on uninstall, the platform runs your unregister script (best effort) and deletes the endpoint. Every accepted delivery raises a webhook-received event that an ExecutionTrigger can react to.

The manifest

api-version: verentis.io/v1
kind: WebhookReceiver

metadata:
  id: sugarcrm-changes
  name: SugarCRM record changes
spec:
  application: sugarcrm-catalog-export        # the app's metadata.id
  source: sugarcrm
  mapping:                                     # JSON pointers into the provider body
    event-type: "{/bean}.{/event}"
    resource-type: /bean
    resource-id: /data/id
    event-id: "{/bean}:{/data/id}:{/data/date_modified}:{/event}"
  settings:                                    # app settings that receive the endpoint details
    url: webhook-url
    token: webhook-token
  register:
    engine: python
    file: /scripts/integrations/sugarcrm/register-webhooks.py
    arguments: ["--receiver", "{webhook.receiver}"]
  unregister:
    engine: python
    file: /scripts/integrations/sugarcrm/unregister-webhooks.py
  run-as:
    service: sugarcrm-webhooks
    scopes: [setting.read, file.read, file.write, file.write]

The app manifest declares the two settings as managed, so install doesn't ask for them:

spec:
  settings:
    - key: webhook-url
      display-name: Webhook URL
      managed: true
    - key: webhook-token
      display-name: Webhook token
      secret: true
      managed: true
metadata.id
Required

The receiver id. It is unique per workspace and is what triggers refer to as receiver. The endpoint id is derived from the workspace and this id, so it stays stable across updates and reinstalls.

spec.application

The owning app's metadata.id. It is required when spec.settings is set, because setting keys are relative to the app.

spec.source
Required

The source recorded on accepted events (e.g. sugarcrm).

spec.mapping

Templates that pull the event fields out of the raw body. A template is a JSON pointer (/data/id), a literal, or text with pointers in braces ({/bean}.{/event}). Pointers missing from the body resolve to empty text, and an empty event-type becomes provider.event. event-id is the deduplication key: a redelivery with the same key is accepted once. If it resolves to empty, a hash of the raw body is used.

spec.settings.url / spec.settings.token

App setting keys (declared in your app manifest's spec.settings) that receive the endpoint URL and token. Declare them with managed: true and make the token a secret. Users are then never asked for them during install, and they appear read-only in the workspace settings.

spec.register / spec.unregister

Scripts to run, in the same shape as an ExecutionTrigger's execute. {webhook.receiver} and {webhook.endpointId} are substituted in arguments.

spec.run-as

The workspace service the register/unregister runs act as.

Authentication

The provider posts to https://<api-host>/v1/webhooks/inbound/<endpoint-id>/provider-events?token=<token>. The URL written to the url setting already includes the token, so give the whole URL to the provider. The token is the only credential, so treat the URL as a secret: don't log it or write it to files that users can read.

Register scripts

Register scripts run every time the manifest is reconciled (install, upgrade, re-sync), so make them idempotent. Upsert subscriptions by a stable name and remove stale ones. If the external system can't be subscribed automatically, for example because the account lacks admin rights, write clear manual instructions somewhere the user will see them and exit successfully.

Settings changes

Changing the app's connection settings does not re-run register automatically. A scheduled job that re-ensures the subscription (and polls for missed changes) is the recommended safety net.

Reacting to deliveries

api-version: verentis.io/v1
kind: ExecutionTrigger
metadata:
  id: sugarcrm-changed
spec:
  on:
    - event: webhook-received
      receiver: sugarcrm-changes
      event-type: "*"
  execute:
    engine: python
    file: /scripts/integrations/sugarcrm/sync-changed.py
    arguments: ["--module", "{webhook.resourceType}", "--id", "{webhook.resourceId}", "--event", "{webhook.eventType}"]
  run-as:
    service: sugarcrm-webhooks

The run receives ids, not the payload. Fetch the changed record from the external system, which also protects you against forged or stale payloads.

Uninstall ordering

If unregister fails, the external system keeps posting to a deleted endpoint and gets 404s until someone removes the subscription there.