Enode Developers

Webhooks

Webhooks are a mechanism that allows your server to receive notifications of events from the Enode system.

Currently, only one webhook is available - a preconfigured webhook called Firehose that reports all supported events occurring within the system. You can configure it using the Update Firehose Webhook endpoint.

One webhook payload will contain up to 100 webhook events.

We will periodically send heartbeat events to your webhook to check its availability as well as inform you about how many events you have queued for delivery. Under normal operations this number is close to zero.

Integrating webhooksCopy link

Webhook deliveries time out after 5 seconds so it's vital that you 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 18 times over 26 hours in increasing intervals after which all events will be deleted and your webhook marked as offline. Any success will reset this counter back to 0.

If your webhook gets marked as invalid it can be reactivated in one of 3 ways:

  • You update the webhook
  • A call to Test Firehose Webhook succeeds
  • The periodical heartbeat gets a success response

Supported eventsCopy link

NameDescription
user:vehicle:discoveredA new vehicle has been discovered attached to a user
user:vehicle:updatedOne or more of a vehicle's properties (as listed in Get Vehicle) has been updated
user:vehicle:deletedA vehicle has been deleted
user:vehicle:smart-charging-status-updatedA vehicle's SmartChargingStatus has updated
user:schedule:execution-updatedA schedule's status has updated
user:vendor-action:updatedAn action has changed state
user:charge-action:updatedAn action has changed state. This is DEPRECATED in favour of user:vendor-action:updated.
system:heartbeatA heartbeat event is sent every 10 minutes to assure that webhook delivery is active
user:charger:discoveredA new charger has been discovered attached to a user
user:charger:updatedOne or more of a charger's properties has been updated
user:charger:deletedA charger has been deleted
user:hvac:discoveredA new HVAC has been discovered attached to a user
user:hvac:updatedOne or more of an HVAC's properties has been updated
user:hvac:updatedAn HVAC has been deleted
user:hvac:target-temperature-updatedThe target temperature for the HVAC has been updated
user:credentials:invalidatedA user credential was marked as invalid

Implementing your webhook endpointCopy link

Your webhook endpoint should expect to receive POST requests bearing the following headers:

HeaderDescription
X-Enode-DeliveryUnique ID identifying the delivered payload
X-Enode-SignatureSignature 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.

Generating a secretCopy link

A cryptographically secure secret should be generated and persisted on your server.

The secret must be provided when you call Update Firehose Webhook. We use the secret to generate a signature on each webhook request. See Verifying a payload signature for instructions on how to use 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");

Verifying a payload signatureCopy link

Requests made 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="

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");
}

Example deliveriesCopy link

The updatedFields value is a list of keys within the vehicle value whose change triggered this update event.

user:vehicle:discoveredCopy link

Delivery containing 1 user:vehicle:discovered event.

The vehicle value follows the same response schema as Get Vehicle.

Sample

[
  {
    "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
    }
  }
]

user:vehicle:updatedCopy link

Delivery containing 1 user:vehicle:updated event.

The vehicle value follows the same response schema as Get Vehicle.

The updatedFields value is a list of keys within the vehicle value whose change triggered this update event.

Sample

[
  {
    "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"],
        "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"]
  }
]

user:vehicle:deletedCopy link

Delivery containing 1 user:vehicle:deleted event.

The vehicle value follows the same response schema as Get Vehicle.

Sample

[
  {
    "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
    }
  }
]

user:vehicle:smart-charging-status-updatedCopy link

Delivery containing 1 user:vehicle:smart-charging-status-updated event.

The smartChargingStatus value follows the same response schema as Get Smart Charging Status.

Sample

[
  {
    "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,
        "confidentBatteryCapacity": true,
        "confidentTimeEstimate": true,
        "hasChargeAboveThreshold": true,
        "hasTimeEstimate": true,
        "isCharging": false,
        "isNormalSchedule": true,
        "isPluggedIn": false,
        "isSmartChargeCapable": true,
        "locationIsFresh": true,
        "minimumDelay": true,
        "minimumSavings": true,
        "needsCharge": true,
        "needsSignificantCharge": true,
        "noCommittedDelay": true,
        "priceDataAvailable": true,
        "singleUser": true
      },
      "plan": null
    },
    "updatedFields": [
      // list of fields that have changed on the smartChargingStatus object
      "consideration.hasTimeEstimate"
    ]
  }
]

user:schedule:execution-updatedCopy link

The schedule value follows the same response schema as Get Schedule, and the status value follows the same response schema as Get Schedule Status.

Sample

[
  {
    "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"]
  }
]

user:vendor-action:updatedCopy link

The vendorAction value follows the same response schema as Post Charger Charging Action and Post Vehicle Charging Action.

Sample

[
  {
    "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"]
  }
]

user:charge-action:updatedCopy link

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 the addition of 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.

user:charger:discoveredCopy link

Delivery containing 1 user:charger:discovered event.

Sample

[
  {
    "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,
        "lastUpdated": null
      },
      "information": {
        "id": "39e9f095-de5f-4acc-b964-9ef7563debc7",
        "brand": "Wallbox",
        "model": "Pulsar Plus",
        "year": 2021
      }
    }
  }
]

user:charger:updatedCopy link

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

[
  {
    "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,
        "chargeRate": null,
        "lastUpdated": null
      },
      "information": {
        "id": "39e9f095-de5f-4acc-b964-9ef7563debc7",
        "brand": "Wallbox",
        "model": "Pulsar Plus",
        "year": 2021
      }
    },
    "updatedFields": ["chargeStateIsPluggedIn"]
  }
]

user:charger:deletedCopy link

Delivery containing 1 user:charger:deleted event.

Sample

[
  {
    "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"
      }
    }
  }
]

system:heartbeatCopy link

Delivery containing 1 system:heartbeat event.

Sample

[
  {
    "event": "system:heartbeat",
    "createdAt": "2020-04-07T17:04:26Z",
    "pendingEvents": 0 // the number of events currently pending delivery via webhook
  }
]

user:hvac:discoveredCopy link

Delivery containing 1 user:hvac:discovered event.

Sample

[
  {
    "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"
      }
    }
  }
]

user:hvac:updatedCopy link

Delivery containing 1 user:hvac:updated event.

Sample

[
  {
    "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"]
  }
]

user:hvac:deletedCopy link

Delivery containing 1 user:hvac:deleted event.

Sample

[
  {
    "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"
      }
    }
  }
]

user:hvac:target-temperature-updatedCopy link

Delivery containing 1 user:hvac:target-temperature-updated event.

Sample

[
  {
    "event": "user:hvac:target-temperature-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,
      "currentTemperature": 19,
      "targetTemperature": 20,
      "consumptionRate": 0,
      "information": {
        "brand": "Mill",
        "model": "Mill Glass WiFi panel heater 1200W",
        "displayName": "Kitchen heater",
        "groupName": "Hallway",
        "category": "HEATING"
      }
    },
    "updatedFields": ["targetTemperature"]
  }
]

user:credentials:invalidatedCopy link

Delivery containing 1 user:credentials:invalidated event.

Sample

[
  {
    "event": "user:credentials:invalidated",
    "createdAt": "2021-12-03T12:53:24.491Z",
    "user": {
      "id": "54d421ca-0d16-450f-984b-00200dcd7ef5"
    },
    "vendor": "TESLA"
  }
]