Skip to content

grid-x/example-webhook-receiver

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

77 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🕸️ 🪝 Integrating systems with the gridX webhook subscription API

The webhook subscription API lets XENON users manage webhook subscriptions for an account.

Overview

The gridX webhook API lets you manage webhook subscriptions for an account on the gridX webhook API. A webhook subscription subscribes to one or many event types in the referenced gridX account. If an event occurs with a type matching any webhook subscription in that account, then this event will be sent to each matching webhook subscription's target URL through an HTTP POST request. The web server listening on that target URL is called a webhook receiver.

With this, webhook subscriptions are a powerful tool to build system integrations on top of XENON, that can react programmatically to events.

Webhook subscriptions are always scoped to an account. It is not supported to subscribe to events in multiple accounts at once. For that, you need to create a webhook subscription on the according event types in each account.

In order to be allowed to manage webhook subscriptions, clients must have the policies WebhooksRead and WebhooksWrite in the account, they want to manage webhook subscriptions for.

This guide walks through implementing a webhook receiver to react on events in a gridX account and registering it on the gridX webhook API.

This sequence diagram outlines the process of building and integrating a webhook receiver with the gridX webhook API:

sequenceDiagram
    Actor developer
    alt development
      developer->>gridX: create webhook subscription ("active": false)
      gridX->>developer: HMAC secret
      developer->>webhook receiver: deploy with HMAC secret
      developer->>webhook receiver: expose on target URL
      developer->>gridX: trigger ping webhook event
      gridX->>webhook receiver: ping webhook event
      webhook receiver->>webhook receiver: verify HMAC digest
      webhook receiver->>webhook receiver: log ping event
      webhook receiver->>gridX: 204 
      developer->>gridX: update webhook subscription ("active": true)
    end
    alt runtime
      gridX->>+webhook receiver: webhook event
      webhook receiver->>webhook receiver: verify HMAC digest
      webhook receiver->>webhook receiver: process event
      webhook receiver->>gridX: 204
    end
Loading

Supported event types

We support the following event types on a webhook subscription:

  • appliance/create: A new appliance has been created.
  • appliance/delete: An appliance has been deleted.
  • appliance/offline: An appliance's state switched offline.
  • appliance/online: An appliance's state switched online.
  • appliance/upate: An appliance has been update.
  • ev/charge-failed: Charging has failed for an electric vehicle.
  • ev/charge-started: Charging has started for an electric vehicle.
  • ev/charge-stopped: Charging has stopped for an electric vehicle.
  • ev/control (deprecated)
  • ev/create: A new electric vehicle has been created. (deprecated)
  • ev/delete: An electric vehicle has been deleted. (deprecated)
  • ev/infeasible-charging-goals
  • ev/measurement
  • ev/plugged: An electric vehicle has been plugged into a charging station.
  • ev/unplugged: An electric vehicle has been plugged out of a charging station.
  • ev/update: An electric vehicle has been updated. (deprecated)
  • gateway/create: A new gateway has been created.
  • gateway/offline: A gateway's state switched offline.
  • gateway/online: A gateway's state switched online.
  • grid-signal-processor/limitation-of-power-consumption/set: The grid signal processor has set a new limitation of power consumption.
  • grid-signal-processor/limitation-of-power-consumption/unset: The grid signal processor has unset a limitation of power consumption.
  • inverter/status: The status of an inverter has changed.
  • system/action

Handling webhook event requests

Events are sent via HTTP requests to webhook receivers.

Important

For receiving and processing webhook events successfully, make sure that your webhook receiver implementation fulfills these criteria:

  • The registered target URL starts with the https scheme and is secured by a trusted TLS certificate.
    • Self-signed certificates are not supported.
  • The webhook receiver accepts POST requests on the registered target URL.
    • We use request path and query parameters exactly as specified on the webhook subscription's target URL. We don't alter or append anything.
    • Keep in mind to URL-encode path segments and query parameters should you use any.
  • The webhook receiver responds to the request within a 5 seconds timeout.
  • The webhook receiver returns any of these status codes: 200 OK, 201 Created, 202 Accepted, 204 No Content.

Request body

Important

The event itself will be sent in the HTTP request's body.

For its schema, please refer to the callback section on the POST /accounts/{accountID}/webhooks endpoint.

The schema adheres to the CloudEvents v1.0.1 specification, which defines the message envelope. Its type property acts as a discriminator for deserializing the data property into a concrete event data type. All possible event data schemas can be found in the OpenAPI specification.

You might also find the example webhook receiver OpenAPI specification helpful.

OpenAPI code generation

If you don't want to implement the OpenAPI specification yourself, you can also leverage tools to generate the HTTP server's code from the OpenAPI specification (see openapi-generator for multi-language support or oapi-codegen for Golang).

While you might need to tweak the templates used for the code generation and fine tune the results, it's a good way to get started in our experience.

To generate server stubs in a language of your choice using OpenAPI Generator, use the following command:

openapi-generator generate -g <server> -o examples/<servername> -i your-webhook-receiver-spec.yaml
# or only for generating schemas
openapi-generator generate -g <server> -o examples/<servername> -i ./webhook-event-components.yaml 

Sending a ping event

Note that the WebhookEvent schema in the OpenAPI specification lists ping as possible event type value for events, even though this is not a valid event type to subscribe to on creating a webhook subscription.

ping is a special event type that can only be used for testing the integration of your webhook receiver with the gridX webhook subscription API. ping events only serve testing purposes!

A request with an empty request body can be sent to the POST /accounts/{accountID}/webhooks/{webhookID}/ping endpoint in order to trigger a ping event for a webhook subscription.

Expected response

Important

This is an overview of how we will process the HTTP responses returned by webhook receivers:

Success: The following response codes will be considered successful.

  • 200 OK
  • 201 Created
  • 202 Accepted
  • 204 No Content

Transient errors: The following responses will be considered to be transient errors. Requests will be retried up to five more times with exponential backoff.

  • 408 Request Timeout
  • 429 Too Many Requests
  • 500 Internal Server Error
  • 502 Bad Gateway
  • 503 Service Unavailable
  • 504 Gateway Timeout

Gone: A 410 Gone response code will lead to automated deletion of the webhook subscription.

Permanent errors: All other response codes will be considered to be permanent errors and won't be retried.

If we receive a successful response, we will ignore the response's body. Otherwise, we will try to parse the response body as arbitrary JSON object and log it.

Managing webhook subscriptions on the API

When you finished implementing your webhook receiver, you can register a webhook subscription for it on the API by calling the create webhook subscription endpoint:

We recommend creating the webhook subscription first deactivated to fetch the HMAC secret and test it first.

POST https://api.gridx.de/accounts/{{accountID}}/webhooks
Accept: application/json
Content-Type: application/json
Authorization: Bearer {{ bearerToken }}

{
  "eventTypes": [
    "gateway/online",
    "gateway/offline"
  ],
  "targetURL": "https://example.com/hooks/xenon",
  "active": false
}

Make sure to extract the following values from the response body:

  • ìd
  • secret

Now you can test your webhook receiver as many times you like with the ping endpoint:

POST https://api.gridx.de/accounts/{{accountID}}/webhooks/{{id}}/ping
Accept: application/json
Content-Type: application/json
Authorization: Bearer {{ bearerToken }}

{}

Make sure to check the requests' signatures with the HMAC secret.

Finally, if the tests have been successful, you can use the update endpoint to activate your webhook subscription:

PUT https://api.gridx.de/accounts/{{accountID}}/webhooks/{{id}}
Accept: application/json
Content-Type: application/json
Authorization: Bearer {{ bearerToken }}

{
  "eventTypes": [
    "gateway/online",
    "gateway/offline"
  ],
  "targetURL": "https://example.com/hooks/xenon",
  "active": true
}

We recommend generating a new HMAC secret at least every six months by leveraging the rotate webhook subscription secret endpoint

Best practices

Verify request integrity

Important

We strongly advise you to verify each received HTTP request's integrity.

For that, we rely on hash-based message authentication code (HMAC) digests.

When you create a new webhook subscription in your account, we generate a random secret for it and return it to you in the creation request's response body.

Important

Retrieving the secret again from our API is not possible! Make sure, you persist this secret securely, so that no one else can access it!

For each webhook event we send to the webhook subscription's target URL, we will then calculate an HMAC-SHA-512 digest of the request body with this secret. This digest will then be appended to the X-Signature header of each outgoing request.

The X-Signature header has the following format:

X-Signature: sha512=<digest 1>[, sha512=<digest 2>, ...]

Each digest is prefixed with sha512=. Multiple digests are separated by , (comma and whitespace). For example:

X-Signature: sha512=a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890
X-Signature: sha512=a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890, sha512=b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0

In order to verify a request's integrity, we recommend implementing these steps on your receiver:

  1. Retrieve your securely stored copy of the secret.
  2. Calculate the HMAC-SHA-512 digest of the received request body with the secret and encode it to hexadecimal.
  3. For each signature provided in the X-Signature header:
    • Remove the sha512= prefix to get only the digest.
    • Compare your calculated digest with the received digest using a constant-time comparison function.
  4. If any of the provided signatures match your calculated digest, the webhook is considered of integrity.
  5. Requests with an invalid or missing X-Signature header must be rejected.
    • 401 Unauthorized with an empty response body should be returned.

Have a look at our Golang code example for verifying a request's integrity.

Secret rotation

The Webhook Subscription API provides an endpoint to rotate a webhook subscription's HMAC secret.

To not break running webhook receivers, rotating the HMAC secret will not remove the old secret immediately. Instead, requests will be signed with both the old and the new secret for three days and both digests will be appended to the requests' X-Signature header. After these three days, the old secret will be finally dropped and requests will only be signed with the new secret.

Warning

We will only sign requests with the five most recent HMAC secrets for a webhook subscription to ensure that our requests don't exceed the header size limits of common web servers.

This means for you, that you shouldn't call the secret rotation five times after each other without actually replacing the HMAC secret key on your receiver with one of the returned secrets.

Handling duplicate events

Due to the nature of network connectivity the same event may be sent more than once. We recommend making your event processing idempotent to handle duplicate events.

One way of doing this is to log events that you’ve processed and to skip processing for already-logged events.

Webhook event ordering

The order, in which you might receive webhook events won't be guaranteed. For example, retrying event delivery on temporary failures (e.g. due to network problems) can lead to the affected event being received after a newer event.

Webhook receiver code examples

  • Node.js/Express: Hand-written webhook receiver that logs the IDs of appliances switching online to the console.
  • Go: A server with openapi-generator[^1] generated stubs for multiple event types.
  • Python/aiohttp: A server with openapi-generator generated stubs for multiple event types. It prints out appliance/online events.
  • Reactive webapp: A webhook receiver that relays events to a React client app through websockets.

About

Examples for gridX webhook receivers in different languages/frameworks

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors