Skip to main content


Webhooks can be used to automatically receive notifications of events that happen, when they happen.

For example, you can be notified when an order's credits are retired.

When you receive an event, you can process and act on it as you need.


Steps to receive webhook events

  1. Expose a webhook endpoint on your servers ('the webhook receiver')
  2. Register your endpoint to start receiving events
  • These steps must be repeated for both live and test modes independently

  • Step 1: Expose a webhook endpoint on your servers

    Create an HTTP endpoint to act as your webhook receiver, just like you would for any web page or API endpoint.

    Take note of its public URL. You will need it in the next step to register the endpoint.

    Note: Lune accepts HTTPS but not HTTP endpoints, even for testing.

    Step 2: Register your endpoint to start receiving events

    You can register your endpoint via the dashboard or API.

    An endpoint registered via the dashboard is going to receive either test or live events, depending on whether 'Test mode' was on or off.

    Endpoints registered via the dashboard receive events for all Accounts and Client Accounts belonging to the organisation.

    Registering your endpoint via the API allows a more granular control of the events that you will receive.

  • Webhooks never mix live and test events.

    Webhooks created with test API Keys deliver test events.

    Similarly, webhooks created with live API Keys deliver live events.

  • webhook-form

  • Sample request
  • curl "" \
      -X POST \
      -H "Authorization: Bearer $API_KEY" \
      -H "Content-Type: application/json" \
      -d '
            "url": ""
  • Sample response
  • {
      "id": "kjmkOq7zXd139gAE9WALWQ8ZGVD7ExNz",
      "url": "",
      "enabled": true,
      "account_type": "live",
      "account_ids": [],
      "created_at": "2000-04-12T23:20:50.52Z",
      "secret": "secret-xxxx"


    Lune delivers events every time an order transitions from one status to another.

    Each request to your webhook receiver includes a batch of events.

    Process event_types relevant to your use case and ignore the rest.

    Your endpoint must return a successful status code (2xx) within 30 seconds to acknowledge the delivery of the events.

    Unacknowledged events are redelivered until acknowledged.

  • Event types
  • event_type:
      type: string
        - order.received
        - order.placed
        - order.paid
        - order.retiring
        - order.cancelled
        - order.failed
        - order.completed

    System properties and guarantees

    • Ordering - events are strictly ordered and are delivered in the same order as they have occurred in Lune's system. The ordering guarantee applies to events belonging to the same Account or Client Account. You may receive events belonging to different Accounts or Client Accounts out of order

    • At-least-once - events are delivered at least once. Unacknowledged events are redelivered until acknowledged with a successful response status code (2xx). Backoff policies are applied if, after several attempts, we are unable to successfully deliver events

    • Head-of-line blocking - given the previous two guarantees, it follows that a few unacknowledged events may hold up several events in your webhook queue

    • Timeout - requests to your webhook receiver time out after 30 seconds. Unacknowledged requests are redelivered

    • HTTPS - only https requests are supported

    • Signed requests - all requests are signed to ensure authenticity and integrity


    Your webhook receiver may receive some events multiple times ('At-least-once' guarantee). You must ensure you can handle receiving duplicate events.

    In order to do so, use your preferred method or one of the following:

    1. Track event_ids: ignore events that have an event_id that you have processed already

    2. Track sequences: for each account, ignore events that have a lower lexicographical sequence

    Authenticity and integrity

    Lune signs all webhook request bodies and includes the signature in a Lune-HMAC header.

    This enables you to verify that events were sent by Lune and were not tampered with by third parties.

    The Lune-HMAC header has the following format

  • Lune-HMAC
  • timestamp=TIMESTAMP,organisation=ORGANISATION_ID,v1=HMAC_VALUE,...


    • TIMESTAMP is the Unix epoch of when the payload was signed. It may be used to ascertain the payload's age
    • ORGANISATION_ID is the organisation id associated with the request
    • HMAC_VALUE is the signature. Lune generates the signature using a hash-based message authentication code (HMAC) with SHA-256.

    Verify the signature

    1. Extract TIMESTAMP and the signature from the header

    2. Determine the expected signature by computing an HMAC with the SHA256 hash function:

    3. Expected signature
    4. HMAC-256(SECRET, TIMESTAMP + "." + BODY)


    • SECRET is the signing secret supplied when you registered the webhook endpoint
    • TIMESTAMP is the timestamp in the request's Lune-HMAC header
    • BODY is the request's body
  • Compare the signature in the header to the expected signature, if they match, process the request, otherwise discard it.

  • Webhook request

    Consult the Webhook request page to familiarise yourself with webhook requests.

  • Sample webhook request
  • curl "" \
      -H "Content-Type: application/json" \
      -H "Lune-HMAC: timestamp=TIMESTAMP,organisation=va2AER4JyqnzPkYxJgALg0GeQDoXlWO6,v1=TG9yZW0gSXBzdW0gaXMgc2ltcGx5IGR1bW15IHRleHQgb2YgdGhlIHByaW50aW5nIGFuZCB0eXBlc2V0dGluZyBpbmR" \
      -X POST \
      -d '{
        "events": [
            "api_version": "v1",
            "event_id": "va1BER4JZqnzPkYxJgALg0GeQDoXlWO5",
            "account_id": "za2BEQ4JZgnzPcYxdiLg1feaoXlWO5"
            "event_type": "order.received",
            "sequence": "2021-09-13T16:21:29.067Z",
            "data": {
              "order": {
                "id": "va1BER4JZqnzPkYxJgALg0GeQDoXlWO5",
                "idempotency_key": "5bd808a954e",
                "type": "quantity",
                "status": "complete",
                "currency": "GBP",
                "offset_cost": "7176.00",
                "total_cost": "7696.00",
                "commission": "520.00",
                "quantity": "1040",
                "created_at": "created_at",
                "bundles": [
                    "bundle_id": "va1BEV2VZqnzPkYxJgALg0GeQDoXlWO5",
                    "bundle_name": "Latin America Forestry",
                    "quantity": "1040",
                    "unit_price": "6.90",
                    "gross_unit_price": "7.90",
                    "offset_cost": "7176.00",
                    "insufficient_available_quantity": true
                "projects": [],
                "certificate": null,
                "email": "[email protected]",
                "requested_quantity": "1045",
                "requested_value": "7700",
                "estimate_id": null,