Webhooks
Webhooks are a mechanism that allows your server to receive notifications of events from the Enode system.
One webhook payload will contain up to 100 webhook events.
We will periodically send heartbeat events to your webhooks to check its availability and inform you how many events you have queued for delivery. Under normal operations, this number is close to zero.
Copy linkVersioning
The API version you set when creating the webhook determines the format of the webhook payload. If the API version is not specified when creating the webhook, your current client API version will be the one used. For information on how to change the client API version, see our Versioning referenceAPI. Every webhook event contains a version
attribute that indicates the version used to generate the payload. We recommend you use the version
attribute in your webhook handler to future-proof your implementation, making it easier for you to handle future version changes.
Copy linkIntegrating webhooks
Webhook deliveries time out after 5 seconds, so you must respond with a 200 OK
as fast as possible and offload processing to an async worker, queue, or background thread following industry best practices. Some examples of this are the Github Docs, Shopify Docs and Stripe Docs.
Enode will attempt to deliver your events up to 16 times over 26 hours in increasing intervals, after which all events will be deleted and your webhook marked as isActive=false
. Any success will reset this counter back to 0.
If some of your webhooks gets marked as invalid, it can be reactivated in one of 2 ways:
- You update the webhook
- A call to Test WebhookAPI succeeds
Copy linkSupported events
Name | Description |
---|---|
user:vehicle:discovered | A new vehicle has been discovered attached to a user |
user:vehicle:updated | One or more of a vehicle's properties (as listed in Get VehicleAPI) has been updated |
user:vehicle:deleted | A vehicle has been deleted |
user:vehicle:smart-charging-status-updated | A vehicle's SmartChargingStatus has updated |
user:schedule:execution-updated | A schedule's status has updated |
user:vendor-action:updated | An action has changed state |
user:charge-action:updated | An action has changed state. This is DEPRECATED in favour of user:vendor-action:updated . |
system:heartbeat | A heartbeat event is sent every 10 minutes |
user:charger:discovered | A new charger has been discovered attached to a user |
user:charger:updated | One or more of a charger's properties has been updated |
user:charger:deleted | A charger has been deleted |
user:hvac:discovered | A new HVAC has been discovered attached to a user |
user:hvac:updated | One or more of an HVAC's properties has been updated |
user:hvac:deleted | An HVAC has been deleted |
user:inverter:discovered | A new inverted has been discovered attached to a user |
user:inverter:updated | One or more of a inverter's properties has been updated |
user:inverter:deleted | An inverter has been deleted |
user:credentials:invalidated | A user credential was marked as invalid |
Copy linkImplementing your webhook endpoint
Your webhook endpoint should expect to receive POST
requests bearing the following headers:
Header | Description |
---|---|
X-Enode-Delivery | Unique ID identifying the delivered payload |
X-Enode-Signature | Signature authenticating that Enode is the author of the delivery |
And an application/json
body containing an array of Events, with the following schema:
Sample
[
{
"event": "user:vehicle:updated", // String - name of the event
"createdAt": "2020-04-07T17:04:26Z", // UTC ISO 8601 - time at which the event was triggered
},
...
]
Each Event object may contain additional properties, depending on the event.
Copy linkGenerating a secret
A cryptographically secure secret should be generated and persisted on your server.
When you call Create WebhookAPI, the secret must be provided. We use the secret to generate a signature on each webhook request. See Verifying a payload signature
for instructions on using the secret to verify that the request originated from Enode.
It should be a pseudorandom value of at least 128 bits produced by a secure generator.
Sample
// Node.js example - 256 bits
const crypto = require("crypto");
const secret = crypto.randomBytes(32).toString("hex");
Copy linkVerifying a payload signature
Requests to your endpoint will bear an X-Enode-Signature
header verifying that the request has come from us.
The signature is the HMAC hex digest of the payload, where:
- algorithm =
sha1
- key = your
secret
provided during webhook configuration - payload = The request body (a UTF-8 encoded string containing JSON - be sure not to deserialize it before signature computation)
The signature is then prefixed with "sha1=". A delivery of {"payload":"example"}
signed with example-secret
would have an X-Enode-Signature
of sha1=e417e6fc2e7f8a78c93a35a7b344d36ce179fc8d
.
In Javascript, the signature may be verified as follows:
Sample
// Node.js + Express example
// Read signature from request HTTP header
const enodeSignature = Buffer.from(req.get("X-Enode-Signature"), "utf8");
// Compute signature using your secret and the request payload
const payload = req.body;
const hmac = crypto.createHmac("sha1", <your secret>);
const digest = Buffer.from("sha1=" + hmac.update(payload).digest("hex"), "utf8");
// Check whether they match, using timing-safe equality (don't use ==)
if (!crypto.timingSafeEqual(digest, enodeSignature)) {
throw new Error("Signature invalid");
}
Copy linkExample deliveries
The updatedFields
value is a list of keys within the vehicle
value whose change triggered this update event.
Copy linkuser:vehicle:discovered
Delivery containing 1 user:vehicle:discovered
event.
The vehicle
value follows the same response schema as Get VehicleAPI.
Sample
[
{
"version": "2023-02-01",
"event": "user:vehicle:discovered",
"createdAt": "2021-12-03T13:22:22.919Z",
"user": {
"id": "29a89ebd-7f42-4469-b471-a13d81a4ab16"
},
"vehicle": {
"smartChargingPolicy": {
"isEnabled": false,
"deadline": null
},
"chargeState": {
"isPluggedIn": null,
"isCharging": null,
"batteryLevel": null,
"range": null,
"isChargingReasons": ["DEFAULT"],
"batteryCapacity": null,
"chargeLimit": null,
"chargeRate": null,
"chargeTimeRemaining": null,
"lastUpdated": null,
"isFullyCharged": false
},
"location": {
"latitude": null,
"longitude": null,
"lastUpdated": null
},
"information": {
"id": "2a2134bf-13ff-4bd3-beca-d1cdc360cbb9",
"brand": "Tesla",
"model": "Tesla Model S",
"year": 2021
},
"odometer": {
"distance": null,
"lastUpdated": null
},
"capabilities": {
"chargeState": {
"interventionIds": [],
"isCapable": true
},
"information": {
"interventionIds": [],
"isCapable": true
},
"location": {
"interventionIds": [],
"isCapable": true
},
"odometer": {
"interventionIds": [],
"isCapable": true
},
"startCharging": {
"interventionIds": [],
"isCapable": true
},
"stopCharging": {
"interventionIds": [],
"isCapable": true
},
"smartCharging": {
"interventionIds": [],
"isCapable": true
}
},
"id": "2a2134bf-13ff-4bd3-beca-d1cdc360cbb9",
"isReachable": false,
"lastSeen": null,
"chargingLocationId": null
}
}
]
Copy linkuser:vehicle:updated
Delivery containing 1 user:vehicle:updated
event.
The vehicle
value follows the same response schema as Get VehicleAPI.
The updatedFields
value is a list of keys within the vehicle
value whose change triggered this update event.
Sample
[
{
"version": "2023-02-01",
"event": "user:vehicle:updated",
"createdAt": "2021-12-03T13:22:22.954Z",
"user": {
"id": "29a89ebd-7f42-4469-b471-a13d81a4ab16"
},
"vehicle": {
"smartChargingPolicy": {
"isEnabled": false,
"deadline": null
},
"chargeState": {
"isPluggedIn": null,
"isCharging": null,
"batteryLevel": null,
"range": null,
"isChargingReasons": ["DEFAULT"],
"powerDeliveryState": "UNKNOWN",
"batteryCapacity": null,
"chargeLimit": null,
"chargeRate": null,
"chargeTimeRemaining": null,
"lastUpdated": null,
"isFullyCharged": false
},
"location": {
"latitude": null,
"longitude": null,
"lastUpdated": null
},
"information": {
"id": "2a2134bf-13ff-4bd3-beca-d1cdc360cbb9",
"brand": "Tesla",
"model": "Test vehicle",
"year": 2021
},
"odometer": {
"distance": null,
"lastUpdated": null
},
"capabilities": {
"chargeState": {
"interventionIds": [],
"isCapable": true
},
"information": {
"interventionIds": [],
"isCapable": true
},
"location": {
"interventionIds": [],
"isCapable": true
},
"odometer": {
"interventionIds": [],
"isCapable": true
},
"startCharging": {
"interventionIds": [],
"isCapable": true
},
"stopCharging": {
"interventionIds": [],
"isCapable": true
},
"smartCharging": {
"interventionIds": [],
"isCapable": true
}
},
"id": "2a2134bf-13ff-4bd3-beca-d1cdc360cbb9",
"isReachable": false,
"lastSeen": null,
"chargingLocationId": null
},
"updatedFields": ["information.model"]
}
]
Copy linkuser:vehicle:deleted
Delivery containing 1 user:vehicle:deleted
event.
The vehicle
value follows the same response schema as Get VehicleAPI.
Sample
[
{
"version": "2023-02-01",
"event": "user:vehicle:deleted",
"createdAt": "2021-12-03T13:22:22.968Z",
"user": {
"id": "29a89ebd-7f42-4469-b471-a13d81a4ab16"
},
"vehicle": {
"smartChargingPolicy": {
"isEnabled": false,
"deadline": null
},
"chargeState": {
"isPluggedIn": null,
"isCharging": null,
"batteryLevel": null,
"range": null,
"isChargingReasons": ["DEFAULT"],
"batteryCapacity": null,
"chargeLimit": null,
"chargeRate": null,
"chargeTimeRemaining": null,
"lastUpdated": null,
"isFullyCharged": false
},
"location": {
"latitude": null,
"longitude": null,
"lastUpdated": null
},
"information": {
"id": "2a2134bf-13ff-4bd3-beca-d1cdc360cbb9",
"brand": "Tesla",
"model": "Test vehicle",
"year": 2021
},
"odometer": {
"distance": null,
"lastUpdated": null
},
"capabilities": {
"chargeState": {
"interventionIds": [],
"isCapable": true
},
"information": {
"interventionIds": [],
"isCapable": true
},
"location": {
"interventionIds": [],
"isCapable": true
},
"odometer": {
"interventionIds": [],
"isCapable": true
},
"startCharging": {
"interventionIds": [],
"isCapable": true
},
"stopCharging": {
"interventionIds": [],
"isCapable": true
},
"smartCharging": {
"interventionIds": [],
"isCapable": true
}
},
"id": "2a2134bf-13ff-4bd3-beca-d1cdc360cbb9",
"isReachable": false,
"lastSeen": null,
"chargingLocationId": null
}
}
]
Copy linkuser:vehicle:smart-charging-status-updated
Delivery containing 1 user:vehicle:smart-charging-status-updated
event.
The smartChargingStatus
value follows the same response schema as Get Smart Charging StatusAPI.
Sample
[
{
"version": "2023-02-01",
"event": "user:vehicle:smart-charging-status-updated",
"createdAt": "2020-04-07T17:04:26Z",
"smartChargingStatus": {
"updatedAt": "2021-06-29T00:00:00Z",
"vehicleId": "VEHICLE-XYZ",
"userId": "USER-XYZ",
"vendor": "TESLA",
"state": "CONSIDERING",
"stateChangedAt": "2021-06-29T00:00:00Z",
"consideration": {
"atChargingLocation": true,
"hasTimeEstimate": true,
"isCharging": false,
"isPluggedIn": false
},
"plan": null
},
"updatedFields": [
// list of fields that have changed on the smartChargingStatus object
"consideration.hasTimeEstimate"
]
}
]
Copy linkuser:schedule:execution-updated
The schedule
value follows the same response schema as Get ScheduleAPI, and the status
value follows the same response schema as Get Schedule StatusAPI.
Sample
[
{
"version": "2023-02-01",
"event": "user:schedule:execution-updated",
"createdAt": "2021-10-13T13:08:12.125Z",
"user": {
"id": "41db4c1f-66c0-4a57-b318-7a853972bb83"
},
"schedule": {
"id": "89282680-ee9e-4351-aa88-a3d8c74d9d45",
"chargingLocationId": null,
"targetId": "1231sd3d2332f223423",
"targetType": "vehicle",
"defaultShouldCharge": false,
"isEnabled": true,
"rules": [
{
"hourMinute": {
"to": "16:00",
"from": "15:00"
},
"shouldCharge": true
}
]
},
"status": {
"scheduleId": "89282680-ee9e-4351-aa88-a3d8c74d9d45",
"state": "INACTIVE:INCAPABLE",
"changedAt": "2021-10-13T09:48:29.069Z",
"isCharging": false,
"isChargingExpected": false,
"isChargingExpectedParts": {
"isPluggedIn": false,
"needsCharge": true,
"shouldCharge": false
},
"upcomingTransitions": [
{
"at": "2021-10-13T15:00:00.000Z",
"shouldCharge": true
},
{
"at": "2021-10-13T16:00:00.000Z",
"shouldCharge": false
}
]
},
// List of changed field inside status
"updatedFields": ["changedAt", "state"]
}
]
Copy linkuser:vendor-action:updated
The vendorAction
value follows the same response schema as Post Charger Charging ActionAPI and Post Vehicle Charging ActionAPI.
Sample
[
{
"version": "2023-02-01",
"event": "user:vendor-action:updated",
"createdAt": "2021-10-13T13:08:12.125Z",
"user": {
"id": "41db4c1f-66c0-4a57-b318-7a853972bb83"
},
"vendorAction": {
"id": "89282680-ee9e-4351-aa88-a3d8c74d9d45",
"targetId": "1231sd3d2332f223423",
"targetType": "vehicle",
"kind": "START",
"state": "CONFIRMED",
"createdAt": "2021-10-13T13:04:13.155Z",
"updatedAt": "2021-10-13T13:08:12.125Z",
"completedAt": "2021-10-13T13:08:12.125Z"
},
// List of changed fields inside vendorAction
"updatedFields": ["changedAt", "state"]
}
]
Copy linkuser:charge-action:updated
This is a deprecated event and is replaced by user:vendor-action:updated
. The event name and some nested keys were changed to align with adding HVAC device actions. During the transition period, we will send duplicate user:charge-action:updated
and user:vendor-actions:updated
events, but only for vehicles and chargers.
Copy linkuser:charger:discovered
Delivery containing 1 user:charger:discovered
event.
Sample
[
{
"version": "2023-02-01",
"event": "user:charger:discovered",
"createdAt": "2021-12-03T12:53:24.456Z",
"user": {
"id": "cb4e983f-a838-41f8-ae33-852701c2d3e6"
},
"charger": {
"id": "39e9f095-de5f-4acc-b964-9ef7563debc7",
"lastSeen": "2021-12-03T12:53:24.450Z",
"isReachable": false,
"chargeState": {
"isPluggedIn": false,
"isCharging": false,
"chargeRate": null,
"powerDeliveryState": "UNPLUGGED",
"lastUpdated": null
},
"information": {
"id": "39e9f095-de5f-4acc-b964-9ef7563debc7",
"brand": "Wallbox",
"model": "Pulsar Plus",
"year": 2021
}
}
}
]
Copy linkuser:charger:updated
Delivery containing 1 user:charger:updated
event.
The updatedFields
value is a list of keys within the charger
value whose change triggered this update event.
Sample
[
{
"version": "2023-02-01",
"event": "user:charger:updated",
"createdAt": "2021-12-03T12:53:24.479Z",
"user": {
"id": "cb4e983f-a838-41f8-ae33-852701c2d3e6"
},
"charger": {
"id": "39e9f095-de5f-4acc-b964-9ef7563debc7",
"lastSeen": "2021-12-03T12:53:24.450Z",
"isReachable": false,
"chargeState": {
"isPluggedIn": true,
"isCharging": false,
"powerDeliveryState": "PLUGGED_IN:STOPPED",
"chargeRate": null,
"lastUpdated": null
},
"information": {
"id": "39e9f095-de5f-4acc-b964-9ef7563debc7",
"brand": "Wallbox",
"model": "Pulsar Plus",
"year": 2021
}
},
"updatedFields": [
"chargeState.isPluggedIn",
"chargeState.powerDeliveryState"
]
}
]
Copy linkuser:charger:deleted
Delivery containing 1 user:charger:deleted
event.
Sample
[
{
"version": "2023-02-01",
"event": "user:charger:deleted",
"createdAt": "2021-12-03T12:53:24.491Z",
"user": {
"id": "c1edc7bf-c8a7-4ae2-9258-12e98332e039"
},
"charger": {
"id": "39e9f095-de5f-4acc-b964-9ef7563debc7",
"chargeState": {
"isCharging": false,
"lastUpdated": null
},
"information": {
"id": "39e9f095-de5f-4acc-b964-9ef7563debc7"
}
}
}
]
Copy linksystem:heartbeat
Delivery containing 1 system:heartbeat
event.
Sample
[
{
"version": "2023-02-01",
"event": "system:heartbeat",
"createdAt": "2020-04-07T17:04:26Z",
"pendingEvents": 0 // the number of events currently pending delivery via webhook
}
]
Copy linkuser:hvac:discovered
Delivery containing 1 user:hvac:discovered
event.
Sample
[
{
"version": "2023-02-01",
"event": "user:hvac:discovered",
"createdAt": "2021-12-03T12:53:24.491Z",
"user": {
"id": "54d421ca-0d16-450f-984b-00200dcd7ef5"
},
"hvac": {
"id": "5bd6b882-0691-4ceb-98a8-238c653aceb6",
"lastSeen": "2021-12-21T17:49:03.586Z",
"isReachable": true,
"chargingLocationId": null,
"isActive": false,
"currentTemperature": 19,
"targetTemperature": 18,
"consumptionRate": 0,
"information": {
"brand": "Mill",
"model": "Mill Glass WiFi panel heater 1200W",
"displayName": "Kitchen heater",
"groupName": "Hallway",
"category": "HEATING"
}
}
}
]
Copy linkuser:hvac:updated
Delivery containing 1 user:hvac:updated
event.
Sample
[
{
"version": "2023-02-01",
"event": "user:hvac:updated",
"createdAt": "2021-12-03T12:53:24.491Z",
"user": {
"id": "54d421ca-0d16-450f-984b-00200dcd7ef5"
},
"hvac": {
"id": "5bd6b882-0691-4ceb-98a8-238c653aceb6",
"lastSeen": "2021-12-21T17:49:03.586Z",
"isReachable": true,
"chargingLocationId": "eb9d2d12-5d21-4e95-bee1-731658d259a0",
"isActive": false,
"consumptionRate": 0,
"currentTemperature": 19,
"targetTemperature": {
"deadband": 2,
"temperature": 20
},
"information": {
"brand": "Mill",
"model": "Mill Glass WiFi panel heater 1200W",
"displayName": "Kitchen heater",
"groupName": "Hallway",
"category": "HEATING"
}
},
"updatedFields": ["chargingLocationId"]
}
]
Copy linkuser:hvac:deleted
Delivery containing 1 user:hvac:deleted
event.
Sample
[
{
"version": "2023-02-01",
"event": "user:hvac:deleted",
"createdAt": "2021-12-03T12:53:24.491Z",
"user": {
"id": "54d421ca-0d16-450f-984b-00200dcd7ef5"
},
"hvac": {
"id": "5bd6b882-0691-4ceb-98a8-238c653aceb6",
"lastSeen": "2021-12-21T17:49:03.586Z",
"isReachable": true,
"chargingLocationId": "eb9d2d12-5d21-4e95-bee1-731658d259a0",
"isActive": false,
"currentTemperature": 19,
"targetTemperature": 18,
"consumptionRate": 0,
"information": {
"brand": "Mill",
"model": "Mill Glass WiFi panel heater 1200W",
"displayName": "Kitchen heater",
"groupName": "Hallway",
"category": "HEATING"
}
}
}
]
Copy linkuser:inverter:discovered
Delivery containing 1 user:inverter:discovered
event.
Sample
[
{
"version": "2023-02-01",
"event": "user:inverter:discovered",
"createdAt": "2021-12-03T12:53:24.491Z",
"user": {
"id": "54d421ca-0d16-450f-984b-00200dcd7ef5"
},
"inverter": {
"id": "238759d3-2d85-43d9-82e1-3cb0062c0257",
"vendor": "FRONIUS",
"chargingLocationId": null,
"lastSeen": "2022-04-07T17:04:26Z",
"isReachable": true,
"productionState": {
"productionRate": 1.8,
"isProducing": true,
"lastUpdated": "2022-04-07T16:21:76Z"
},
"information": {
"id": "8d90101b-3f2f-462a-bbb4-1ed320d33bbe",
"brand": "SMA",
"model": "Sunny Boy",
"siteName": "Sunny plant",
"installationDate": "2020-04-05"
},
"location": {
"longitude": 10.757933,
"latitude": 59.911491
}
}
}
]
Copy linkuser:inverter:updated
Delivery containing 1 user:inverter:updated
event.
Sample
[
{
"version": "2023-02-01",
"event": "user:inverter:updated",
"createdAt": "2021-12-03T12:53:24.491Z",
"user": {
"id": "54d421ca-0d16-450f-984b-00200dcd7ef5"
},
"inverter": {
"id": "238759d3-2d85-43d9-82e1-3cb0062c0257",
"vendor": "FRONIUS",
"chargingLocationId": "8d90101b-3f2f-462a-bbb4-1ed320d33bbe",
"lastSeen": "2022-04-07T17:04:26Z",
"isReachable": true,
"productionState": {
"productionRate": 1.8,
"isProducing": true,
"lastUpdated": "2022-04-07T16:21:76Z"
},
"information": {
"id": "8d90101b-3f2f-462a-bbb4-1ed320d33bbe",
"brand": "SMA",
"model": "Sunny Boy",
"siteName": "Sunny plant",
"installationDate": "2020-04-05"
},
"location": {
"longitude": 10.757933,
"latitude": 59.911491
}
},
"updatedFields": ["chargingLocationId"]
}
]
Copy linkuser:inverter:deleted
Delivery containing 1 user:inverter:deleted
event.
Sample
[
{
"version": "2023-02-01",
"event": "user:inverter:deleted",
"createdAt": "2021-12-03T12:53:24.491Z",
"user": {
"id": "54d421ca-0d16-450f-984b-00200dcd7ef5"
},
"inverter": {
"id": "238759d3-2d85-43d9-82e1-3cb0062c0257",
"vendor": "FRONIUS",
"chargingLocationId": "8d90101b-3f2f-462a-bbb4-1ed320d33bbe",
"lastSeen": "2022-04-07T17:04:26Z",
"isReachable": true,
"productionState": {
"productionRate": 1.8,
"isProducing": true,
"lastUpdated": "2022-04-07T16:21:76Z"
},
"information": {
"id": "8d90101b-3f2f-462a-bbb4-1ed320d33bbe",
"brand": "SMA",
"model": "Sunny Boy",
"siteName": "Sunny plant",
"installationDate": "2020-04-05"
},
"location": {
"longitude": 10.757933,
"latitude": 59.911491
}
}
}
]
Copy linkuser:credentials:invalidated
Delivery containing 1 user:credentials:invalidated
event.
Sample
[
{
"version": "2023-02-01",
"event": "user:credentials:invalidated",
"createdAt": "2021-12-03T12:53:24.491Z",
"user": {
"id": "54d421ca-0d16-450f-984b-00200dcd7ef5"
},
"vendor": "TESLA"
}
]