Webhook
Context
The webhook provides updates on certain models in the Energyzero platform. Every time the fields pushed toward the partner are updated, these models are sent as events. It basically acts as a CRUD hook of the database and ensures the latest state for these models is sent towards the partner
Security
A private key is shared with the partner to ensure the payload being sent originates from the Energyzero platform. With the private key, the body of the request is signed and sent as an HTTP header X-Auth-Signature. The private key is only known to the partner, it’s stored encrypted within the EnergyZero platform ensuring that only the platform itself can sign the payloads. The holder of the private key can recreate this header and compare it to the header to guarantee valid content and authenticity of the message.
Steps to create a signature:
- Create an MD5 hash of the full body sent through the webhook
- Encode the hash in base64
- Sign the output with HMAC using sha256 algorithm and the provided private key
- Base64 the output
- Compare the incoming X-Auth-Signature with the output
- Process the content only if the signatures are equal
###Important notes The MD5 hash must be created on the raw body sent as is. If any preprocessing is happening on the body such as alphabetically sorting the fields or re-encoding the JSON message there is a risk that the resulting MD5 hash differs, causing signature decoding to fail.
Golang pseudocode:
// VerifySignature represents pseudocode to create hmac header based on private key and compares it with the incoming signature.
func VerifySignature(privateKey, body []byte, requestSignature string) (bool, error) {
md5Hash := md5.Sum(body)
mdBased := base64.StdEncoding.EncodeToString(md5Hash[:])
message := []byte(mdBased)
mac := hmac.New(sha256.New, []byte(privateKey))
_, err := mac.Write(message)
if err != nil {
return false, err
}
createdHmacHeader := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return createdHmacHeader == requestSignature, nil
}
Guarantees
The webhook sends the events for each update on a model to the given URL as HTTP POST requests with the X-Auth-Signature, X-Event-Id, and X-Attempt headers. The partner implementation should provide a 200 HTTP status whenever the payload is accepted and processed. The webhook will retry sending the update whenever a different status that is not within the 200 range is returned. Any events that the partner is not interested in should also be provided with a 200 status code, this will prevent the EnergyZero platform from retry sending the updates.
Retry mechanism
To ensure that all model updates are received and processed by the partner implementation the EnergyZero platform will retry sending the events for up to 7 days. The updates will retry every 30 minutes The updates will be deleted from the queue after 7 days The webhook does not guarantee the order of delivery (created_at and updated_at are always sent with the models) Each POST request is sent with an X-Attempt header, indicating the number of retries that are sent until now. The initial message will be the first attempt, and the first retry will be the second attempt at sending the message. In the case, the partner is unable to process an event it will be retried a total of 336 times (48 times a day * 7 days). This can result in a lot of noise in the logs and metrics of both the partner and EnergyZero. therefore it’s a good practice to consider the Event Id when setting up monitoring solutions. This allows for the isolation of specific model updates, excluding the retry requests. The Event Id is unique for each model update and is sent along within the event payload in the metadata and is included in the request header X-Event-Id
Event Payloads
The webhook will send events for all updates on the following models: Contract, Invoice, and User. The Event structure exists out of event metadata and the actual model that is being sent.
{
"event_metadata": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"model_name": "Contract",
"reason": "Created"
},
"model": {...}
}
Event Metadata
Each event is sent with a set of metadata describing the event: id: A unique identifier representing the specific model update. It does not change when retrying to send the event. It can be used on the partner side so ensure an event is never processed twice. model_name: Name of the model that was updated. This defines the structure of the model payload of the event. Can be any of User, Contract or Invoice reason: Indicates if the model is created, updated or deleted. Can be any of Created, Updated or Deleted Note that some models also have a deleted_at field which represents a soft deletion of the model. These events are sent with the reason Updated and contain a date of deletion in the deleted_at field.
Model
Each type of model has a different structure, containing the updated data. The latest version of the OpenApi specs describing the structure of each model event can be found here and should be downloaded and included separately with this documentation. Matching sign-ups using ContractLabels
Partners can use contract labels to match sign-ups in their own system with sign-ups in EnergyZero’s platform by using the ContractLabels field in the webhook. To do so, add a query parameter prefixed with l_ to the redirect to your onboarding environment. For example: aanmelden.energie.yourname.tld/?l_customer_id=123456 The ContractLabels field in the webhook will then contain the provided customer_id. By using this field it’s possible to connect one, or many data fields across different systems.