> ## 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.

# Webhooks API — Subscribe to Charging Network Events

> Register HTTP endpoints to receive real-time events from your EV charging network. Verify payloads with HMAC-SHA256 signatures for secure integration.

Dynamo CSMS webhooks push real-time events to your server when things happen on your charging network — sessions start and end, chargers connect or disconnect, alerts fire, and firmware updates complete. Instead of polling the API, you register an HTTPS endpoint and we deliver events as they happen.

## Authentication

All webhook management endpoints require your API key:

```bash theme={null}
Authorization: Bearer YOUR_API_KEY
# or
X-API-Key: YOUR_API_KEY
```

## Available events

| Event                         | Description                                                   |
| ----------------------------- | ------------------------------------------------------------- |
| `charge_point.connected`      | A charge point establishes a WebSocket connection             |
| `charge_point.disconnected`   | A charge point loses its connection                           |
| `charge_point.status_changed` | Connector status changes (Available, Charging, Faulted, etc.) |
| `session.started`             | A charging session begins                                     |
| `session.ended`               | A charging session completes                                  |
| `session.cost_updated`        | Session cost is recalculated mid-session                      |
| `commissioning.started`       | Charger commissioning process begins                          |
| `commissioning.completed`     | Charger is fully commissioned and operational                 |
| `commissioning.failed`        | Commissioning failed — check the error details                |
| `alert.triggered`             | A monitoring alert fires                                      |
| `alert.resolved`              | A previously triggered alert clears                           |
| `firmware.update_started`     | Firmware update begins on a charge point                      |
| `firmware.update_completed`   | Firmware update finishes successfully                         |
| `handover.completed`          | A charger handover to a new operator completes                |

## Create a webhook endpoint

`POST /api/v1/webhooks/endpoints`

<ParamField body="url" type="string" required>
  HTTPS URL where events will be delivered. Must use `https://`.
</ParamField>

<ParamField body="events" type="string[]" required>
  List of event types to subscribe to. Use `["*"]` to subscribe to all events.
</ParamField>

<ParamField body="secret" type="string">
  Signing secret for HMAC-SHA256 payload verification. Store this securely — it is not retrievable after creation.
</ParamField>

```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://yourapp.com/webhooks/dynamo",
    "events": ["session.started", "session.ended", "charge_point.disconnected"],
    "secret": "whsec_your_secret_here"
  }'
```

```json theme={null}
{
  "id": "whe_01HX9K2M4N8P3Q7R",
  "url": "https://yourapp.com/webhooks/dynamo",
  "events": ["session.started", "session.ended", "charge_point.disconnected"],
  "active": true,
  "created_at": "2024-01-20T14:30:00Z"
}
```

## List webhook endpoints

`GET /api/v1/webhooks/endpoints`

Returns all webhook endpoints registered for your account.

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

```json theme={null}
{
  "data": [
    {
      "id": "whe_01HX9K2M4N8P3Q7R",
      "url": "https://yourapp.com/webhooks/dynamo",
      "events": ["session.started", "session.ended"],
      "active": true,
      "created_at": "2024-01-20T14:30:00Z"
    }
  ],
  "total": 1
}
```

## Get a webhook endpoint

`GET /api/v1/webhooks/endpoints/{endpoint_id}`

<ParamField path="endpoint_id" type="string" required>
  The ID of the webhook endpoint to retrieve.
</ParamField>

## Update a webhook endpoint

`PATCH /api/v1/webhooks/endpoints/{endpoint_id}`

<ParamField path="endpoint_id" type="string" required>
  The ID of the webhook endpoint to update.
</ParamField>

<ParamField body="url" type="string">
  New delivery URL for this endpoint.
</ParamField>

<ParamField body="events" type="string[]">
  Replacement list of subscribed event types.
</ParamField>

<ParamField body="active" type="boolean">
  Set to `false` to pause delivery without deleting the endpoint.
</ParamField>

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

## Delete a webhook endpoint

`DELETE /api/v1/webhooks/endpoints/{endpoint_id}`

<ParamField path="endpoint_id" type="string" required>
  The ID of the webhook endpoint to delete.
</ParamField>

Returns `204 No Content` on success.

```bash theme={null}
curl -X DELETE https://api.dynamo-csms.com/api/v1/webhooks/endpoints/whe_01HX9K2M4N8P3Q7R \
  -H "Authorization: Bearer YOUR_API_KEY"
```

## Delivery history

`GET /api/v1/webhooks/endpoints/{endpoint_id}/deliveries`

Retrieve the delivery history for a webhook endpoint. Use this to debug failed deliveries or inspect retry behavior.

<ParamField path="endpoint_id" type="string" required>
  The ID of the webhook endpoint.
</ParamField>

<ParamField query="status" type="string">
  Filter by delivery status: `succeeded`, `failed`, or `pending`.
</ParamField>

<ParamField query="limit" type="number" default="20">
  Number of records to return (max 100).
</ParamField>

```bash theme={null}
curl "https://api.dynamo-csms.com/api/v1/webhooks/endpoints/whe_01HX9K2M4N8P3Q7R/deliveries?status=failed" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json theme={null}
{
  "data": [
    {
      "id": "whd_01HXB3N7P2Q4R8S9",
      "endpoint_id": "whe_01HX9K2M4N8P3Q7R",
      "event": "session.started",
      "status": "failed",
      "response_code": 500,
      "attempts": 3,
      "last_attempted_at": "2024-01-20T15:05:32Z",
      "next_retry_at": "2024-01-20T15:35:32Z"
    }
  ],
  "total": 1
}
```

<ResponseField name="id" type="string">
  Unique delivery record ID.
</ResponseField>

<ResponseField name="event" type="string">
  The event type that triggered this delivery.
</ResponseField>

<ResponseField name="status" type="string">
  Delivery status: `succeeded`, `failed`, or `pending`.
</ResponseField>

<ResponseField name="response_code" type="number">
  HTTP status code returned by your endpoint.
</ResponseField>

<ResponseField name="attempts" type="number">
  Number of delivery attempts made. Dynamo retries failed deliveries with exponential backoff up to 5 times over 24 hours.
</ResponseField>

## Webhook payload structure

Every event is delivered as an HTTP POST with a JSON body:

```json theme={null}
{
  "event": "session.started",
  "timestamp": "2024-01-20T14:30:00Z",
  "data": {
    "session_id": "sess_abc123",
    "charge_point_id": "cp_001",
    "connector_id": 1,
    "id_tag": "driver@example.com"
  }
}
```

<ResponseField name="event" type="string">
  The event type (e.g., `session.started`).
</ResponseField>

<ResponseField name="timestamp" type="string">
  ISO 8601 timestamp of when the event occurred.
</ResponseField>

<ResponseField name="data" type="object">
  Event-specific payload. Structure varies by event type.
</ResponseField>

## Signature verification

When you provide a `secret` at endpoint creation, every delivery includes an `X-Webhook-Signature` header. The value is an HMAC-SHA256 digest of the raw request body, prefixed with `sha256=`.

**Always verify this signature before processing a webhook payload.** This confirms the request came from Dynamo CSMS and the body was not tampered with in transit.

<Warning>
  Compute the HMAC over the raw request body bytes before any JSON parsing. Parsing and re-serializing the payload may alter whitespace and invalidate the signature.
</Warning>

<CodeGroup>
  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_webhook(payload_body: bytes, signature_header: str, secret: str) -> bool:
      """Return True if the webhook signature is valid."""
      expected = "sha256=" + hmac.new(
          secret.encode("utf-8"),
          payload_body,
          hashlib.sha256
      ).hexdigest()
      return hmac.compare_digest(expected, signature_header)

  # In your request handler:
  # raw_body = request.get_data()  # Flask example
  # sig = request.headers.get("X-Webhook-Signature")
  # if not verify_webhook(raw_body, sig, "whsec_your_secret_here"):
  #     return "Unauthorized", 401
  ```

  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function verifyWebhook(rawBody, signatureHeader, secret) {
    const expected =
      'sha256=' +
      crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
    return crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signatureHeader)
    );
  }

  // In your Express handler:
  // app.post('/webhooks/dynamo', express.raw({ type: 'application/json' }), (req, res) => {
  //   const sig = req.headers['x-webhook-signature'];
  //   if (!verifyWebhook(req.body, sig, 'whsec_your_secret_here')) {
  //     return res.status(401).send('Unauthorized');
  //   }
  //   const event = JSON.parse(req.body);
  //   // handle event...
  //   res.status(200).send('OK');
  // });
  ```
</CodeGroup>

<Tip>
  Respond with a `2xx` status code within 10 seconds. If your handler needs more time to process an event, acknowledge the delivery immediately and process asynchronously.
</Tip>
