> ## Documentation Index
> Fetch the complete documentation index at: https://dynamo-csms.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Set Up Webhooks to Receive Real-Time Charge Events

> Register webhook endpoints, verify HMAC signatures, handle event payloads, and monitor delivery history for Dynamo CSMS real-time notifications.

Dynamo CSMS sends webhook events to URLs you register whenever something significant happens — a session starts, a charge point goes offline, or commissioning completes. This guide shows you how to set up endpoints, verify incoming payloads, and troubleshoot failed deliveries.

## Register a webhook endpoint

Create an endpoint by providing the URL that should receive events, the list of event types you want to subscribe to, and a secret used to sign payloads.

```bash theme={null}
curl -X POST https://api.dynamo-csms.com/api/v1/webhooks/endpoints \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/webhooks/dynamo",
    "events": [
      "charge_point.connected",
      "charge_point.disconnected",
      "session.started",
      "session.ended",
      "alert.triggered",
      "commissioning.completed"
    ],
    "secret": "whsec_your_random_secret_here",
    "description": "Production event receiver"
  }'
```

Response:

```json theme={null}
{
  "id": "whe_01HXUVWXYZ12345678",
  "url": "https://your-app.example.com/webhooks/dynamo",
  "events": [
    "charge_point.connected",
    "charge_point.disconnected",
    "session.started",
    "session.ended",
    "alert.triggered",
    "commissioning.completed"
  ],
  "status": "active",
  "created_at": "2026-05-01T11:00:00Z"
}
```

<Tip>
  Generate your `secret` using a cryptographically secure random generator (e.g., `openssl rand -hex 32`). Store it securely — you'll need it to verify every incoming payload.
</Tip>

## Available event types

| Event                       | Fired when                                    |
| --------------------------- | --------------------------------------------- |
| `charge_point.connected`    | A charge point establishes an OCPP connection |
| `charge_point.disconnected` | A charge point loses its OCPP connection      |
| `charge_point.fault`        | A charge point reports a fault or error code  |
| `session.started`           | A driver begins a charging session            |
| `session.ended`             | A charging session completes                  |
| `session.authorized`        | An RFID or app authorization is granted       |
| `alert.triggered`           | An alert rule threshold is exceeded           |
| `commissioning.completed`   | A charge point commissioning check finishes   |
| `commissioning.failed`      | A commissioning check fails                   |
| `billing.invoice_ready`     | A monthly invoice has been generated          |

## Webhook payload format

Every event payload shares a common envelope with an `event` type, a unique `id`, a timestamp, and an `object` containing the relevant data.

Example `session.started` payload:

```json theme={null}
{
  "id": "evt_01HXABCDEF12345678",
  "event": "session.started",
  "created_at": "2026-05-01T08:12:05Z",
  "api_version": "2026-05-01",
  "object": {
    "id": "ses_01HXGHIJKL23456789",
    "charge_point_id": "cp_01HX5P2QRSTUVWXYZ0",
    "connector_id": 1,
    "driver_id": "drv_01HXDEFGH123456789",
    "authorization_method": "rfid",
    "started_at": "2026-05-01T08:12:00Z",
    "tariff_id": "tar_01HX8ABCDE12345678",
    "site_id": "site_01HXMNOPQR34567890"
  }
}
```

## Verify webhook signatures

Dynamo signs every request with an HMAC-SHA256 signature derived from the raw request body and your endpoint secret. Always verify the signature before processing a payload.

The signature is sent in the `X-Dynamo-Signature` header as `sha256=<hex_digest>`.

<Tabs>
  <Tab title="Node.js">
    ```javascript theme={null}
    const crypto = require('crypto');

    function verifySignature(rawBody, signatureHeader, secret) {
      const expected = 'sha256=' + crypto
        .createHmac('sha256', secret)
        .update(rawBody)
        .digest('hex');

      // Use timingSafeEqual to prevent timing attacks
      const headerBuf = Buffer.from(signatureHeader);
      const expectedBuf = Buffer.from(expected);

      if (headerBuf.length !== expectedBuf.length) return false;
      return crypto.timingSafeEqual(headerBuf, expectedBuf);
    }

    // Express example
    app.post('/webhooks/dynamo', express.raw({ type: 'application/json' }), (req, res) => {
      const sig = req.headers['x-dynamo-signature'];
      if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
        return res.status(401).send('Invalid signature');
      }
      const event = JSON.parse(req.body);
      console.log('Received event:', event.event);
      res.sendStatus(200);
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import hmac
    import hashlib

    def verify_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
        expected = 'sha256=' + hmac.new(
            secret.encode(),
            raw_body,
            hashlib.sha256
        ).hexdigest()
        return hmac.compare_digest(expected, signature_header)

    # Flask example
    from flask import Flask, request, abort
    import os

    app = Flask(__name__)

    @app.route('/webhooks/dynamo', methods=['POST'])
    def webhook():
        sig = request.headers.get('X-Dynamo-Signature', '')
        if not verify_signature(request.data, sig, os.environ['WEBHOOK_SECRET']):
            abort(401)
        event = request.get_json()
        print(f"Received event: {event['event']}")
        return '', 200
    ```
  </Tab>

  <Tab title="Ruby">
    ```ruby theme={null}
    require 'openssl'

    def verify_signature(raw_body, signature_header, secret)
      digest = OpenSSL::HMAC.hexdigest('SHA256', secret, raw_body)
      expected = "sha256=#{digest}"
      ActiveSupport::SecurityUtils.secure_compare(expected, signature_header)
    end

    # Rails example
    def webhook
      sig = request.headers['X-Dynamo-Signature']
      unless verify_signature(request.raw_post, sig, ENV['WEBHOOK_SECRET'])
        return head :unauthorized
      end
      event = JSON.parse(request.raw_post)
      Rails.logger.info "Received event: #{event['event']}"
      head :ok
    end
    ```
  </Tab>
</Tabs>

<Warning>
  Always read the raw request body bytes before parsing JSON. Many frameworks reformat the body during parsing, which will cause signature verification to fail.
</Warning>

## Retry behaviour

If your endpoint returns a non-2xx HTTP status code or times out (30 second limit), Dynamo retries the delivery with exponential backoff:

| Attempt   | Delay      |
| --------- | ---------- |
| 1st retry | 5 seconds  |
| 2nd retry | 30 seconds |
| 3rd retry | 5 minutes  |
| 4th retry | 30 minutes |
| 5th retry | 2 hours    |

After 5 failed attempts, the delivery is marked as `failed` and no further retries occur. You can manually replay failed deliveries from the dashboard or using the deliveries API.

## Manage endpoints

<CodeGroup>
  ```bash List endpoints theme={null}
  curl -X GET https://api.dynamo-csms.com/api/v1/webhooks/endpoints \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```bash Get a single endpoint theme={null}
  curl -X GET https://api.dynamo-csms.com/api/v1/webhooks/endpoints/whe_01HXUVWXYZ12345678 \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```bash Update endpoint events theme={null}
  curl -X PATCH https://api.dynamo-csms.com/api/v1/webhooks/endpoints/whe_01HXUVWXYZ12345678 \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"events": ["session.started", "session.ended"]}'
  ```

  ```bash Delete an endpoint theme={null}
  curl -X DELETE https://api.dynamo-csms.com/api/v1/webhooks/endpoints/whe_01HXUVWXYZ12345678 \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```
</CodeGroup>

## View delivery history

Inspect past delivery attempts to debug failures or confirm events were received.

```bash theme={null}
curl -X GET "https://api.dynamo-csms.com/api/v1/webhooks/endpoints/whe_01HXUVWXYZ12345678/deliveries?limit=10" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Response:

```json theme={null}
{
  "deliveries": [
    {
      "id": "del_01HXSTUVWX45678901",
      "event_id": "evt_01HXABCDEF12345678",
      "event_type": "session.started",
      "status": "delivered",
      "http_status": 200,
      "attempts": 1,
      "last_attempted_at": "2026-05-01T08:12:06Z",
      "delivered_at": "2026-05-01T08:12:06Z"
    },
    {
      "id": "del_01HXYZ012345678901",
      "event_id": "evt_01HXIJKLMN98765432",
      "event_type": "alert.triggered",
      "status": "failed",
      "http_status": 503,
      "attempts": 5,
      "last_attempted_at": "2026-05-01T10:12:06Z",
      "delivered_at": null
    }
  ],
  "total": 2
}
```
