Skip to content

Webhooks

Webhooks make it possible to create a subscription from an external system to events in Lime CRM. You can subscribe to new, update, delete and restore for all available Limetypes, Webhooks system events and any registered custom event.

Features

  • It is possible to list available events, current subscriptions and register a new or update an existing subscription.
  • Subscriptions can be signed with a secret, to validate contents and sender
  • Subscriptions can be created via Lime Admin or via API
  • Webhooks can be extended with new events by registering custom events

Installation

  1. Check that the requirements are met.

  2. Copy and run the below poetry command to add the python dependency to the solution.

    poetry add lime-webhooks
    
  3. Build and deploy your solution. A restart of the Lime CRM server is required afterwards.

Administration via API

To be able to call the API endpoints, the user must be an Administrator or part of the dedicated Lime CRM group Webhook Users.

IMPORTANT: Users in that group will have the possibility to modify all existing subscriptions.

Administration via Lime Admin

Webhooks can be administrated and created via Lime Admin

Lime Admin

Securing webhooks

To secure a webhook subscription you can sign the payload with a secret. When creating a subscription add the optional secret.

All deliveries in that subscription are then signed with the secret using HMAC SHA-256. The signature is sent in the header X-Lime-Signature. What is signed depends on the signature version of the subscription, which is decided by the version of the API the subscription was created through. The version is returned as signature_version by the API and cannot be changed afterwards. To move a subscription to another version, delete it and create it again through the other API version.

Every delivery, signed or not, carries these headers:

Header Value
X-Lime-Webhook-Id Id of the subscription
X-Lime-Delivery-Id Id of the delivery, a UUID. Retries of a delivery reuse it
X-Lime-Message-Id Id of the Lime CRM event that caused the delivery, if any
X-Lime-Delivery-Timestamp Unix time in seconds of the first delivery attempt. Only on version 2
X-Lime-Signature sha256=<hex> for version 1, v2=<hex> for version 2. Only when a secret is set

Signature version 1

Subscriptions created through /api/v1/subscription/ get version 1. The signature is the HMAC SHA-256 of the raw request body, hex encoded, sent as X-Lime-Signature: sha256=<hex>.

The signature depends on the secret and the body only. The same body always gives the same signature, so a captured delivery passes verification if it is sent again later.

Signature version 2

Subscriptions created through /api/v2/subscription/ get version 2. The signature covers the delivery id, the delivery timestamp and the body, so a receiver can reject a delivery that is old or that it has already processed.

The signed string is built from the headers and the raw request body:

v2:<X-Lime-Delivery-Id>:<X-Lime-Delivery-Timestamp>:<sha256 hex of the raw body>

The signature is the HMAC SHA-256 of that string, hex encoded, sent as X-Lime-Signature: v2=<hex>. A version 2 delivery does not carry a sha256= signature.

To verify a delivery:

  1. Read X-Lime-Delivery-Id, X-Lime-Delivery-Timestamp and X-Lime-Signature.
  2. Reject the delivery if the timestamp is more than 300 seconds away from the current time.
  3. Hash the raw request body with SHA-256. Do not re-serialize a parsed body, since that may not reproduce the bytes that were signed.
  4. Build the signed string, compute the HMAC with the secret and compare it to the signature with a constant time comparison.
  5. Reject the delivery if the delivery id has already been processed within the freshness window.
import hashlib
import hmac
import time

WINDOW_SECONDS = 300


def verify(secret: str, headers: dict, raw_body: bytes) -> bool:
    timestamp = int(headers["X-Lime-Delivery-Timestamp"])
    if abs(time.time() - timestamp) > WINDOW_SECONDS:
        return False
    body_hash = hashlib.sha256(raw_body).hexdigest()
    signed = f"v2:{headers['X-Lime-Delivery-Id']}:{timestamp}:{body_hash}"
    expected = "v2=" + hmac.new(
        secret.encode(), signed.encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, headers["X-Lime-Signature"])

A failed delivery is retried with the delivery id, timestamp and signature of the first attempt. A retry that reaches the receiver after the freshness window is rejected by the receiver. See delivery attempts for the limit this puts on the retry configuration.

Custom header

Lime Webhooks also supports custom headers, which can be used for authentication. The most common approach is signing the payload with a shared secret.

Requirements

  • lime-webhooks is added as a dependency to the Lime CRM solution. It's included in Lime shared cloud.
  • Lime CRM server version 13.2 or newer.
  • CRM server MUST NOT use LDC for writes (ie requires use_ldc_for_writes = False in %programdata\LIME Pro Server\Web Server\configs\config.ini). This is the default setting since Lime CRM 13.0
  • A Lime CRM Group called Webhook Users