Skip to content

Reference implementation

We have a GitHub repository that contains reference implementations of webhook notifications and API clients for interacting with the Energy Coordination API. This repository contains fully working examples with auto-generated code using OpenApi Generator.

Important files

The OpenApi generated code can be a bit verbose, but this page outlines the most important implementation details in order to quickly get started. The most pertinent files in the repository are:

C#

Java

TypeScript

You can also see these files in the following sections.

Webhook server implementation

There are two main components to handling the webhook server implementation for receiving notifications from the Energy Coordination API; receiving the notification, and verifying the signature.

Receiving the events should be done in the following manner:

NotificationApiControllerImplementation.cs
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Partner.Api.Controllers;
using Partner.Api.Filters;
using Partner.Api.Models;
namespace Partner.Api.Implementations;
/// <summary>
/// Reference implementation of the notification event webhook
/// </summary>
public class NotificationApiControllerImplementation(
ILogger<NotificationApiControllerImplementation> logger
) : NotificationApiController
{
/// <inheritdoc/>
[BodySignatureFilter]
public override async Task<IActionResult> NotifyPost(
[FromHeader(Name = "x-payload-signature"), Required] string xPayloadSignature,
[FromBody] EventNotification eventNotification
)
{
// Fetch calculated signature from BodySignatureFilter
if (
!HttpContext.Items.TryGetValue(BodySignatureFilter.ItemsKey, out var computedHash)
|| computedHash == null
)
{
logger.LogWarning("Unable to compute hash for incoming request");
return Problem(statusCode: StatusCodes.Status500InternalServerError);
}
if (computedHash.ToString() != xPayloadSignature)
{
return BadRequest();
}
// Do not perform long processing before returning a 200 OK response
await SaveEventForLaterProcessingAsync(eventNotification);
// It is important to return a 200 OK response to acknowledge receipt of event
return Ok();
}
private static Task SaveEventForLaterProcessingAsync(EventNotification eventNotification)
{
// Save the event to a queue or database for later processing
// For example, you can save the event to a database for later processing
// using Entity Framework Core or Dapper
// or enqueue the event to a message broker like Azure Service Bus or RabbitMQ
return Task.CompletedTask;
}
}

Calculate signature:

BodySignatureFilter.cs
using System;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
namespace Partner.Api.Filters;
/// <summary>
/// Filter to compute the HMAC SHA256 signature of the request body.
/// </summary>
public class BodySignatureFilter : IAsyncResourceFilter
{
/// <summary> The key for the calculated signature in <see cref="HttpContext.Items"/> </summary>
public const string ItemsKey = "HmacSha256Signature";
/// <summary> The config key for the signing key</summary>
public const string SigningKeyConfigKey = "SIGNING_KEY";
private readonly string _signingKey;
/// <summary>
/// Instantiate a new BodySignatureFilter.
/// </summary>
public BodySignatureFilter(IConfiguration configuration)
{
// Fetch signing secret from configuration, should be injected via an Environment variable
string signingKey = configuration.GetValue<string>(SigningKeyConfigKey);
ArgumentNullException.ThrowIfNull(signingKey);
_signingKey = signingKey;
}
/// <inheritdoc/>
public async Task OnResourceExecutionAsync(
ResourceExecutingContext context,
ResourceExecutionDelegate next
)
{
var request = context.HttpContext.Request;
if (request.ContentType == null || !request.ContentType.Contains("application/json"))
{
await next();
return;
}
request.EnableBuffering();
using var hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(_signingKey));
byte[] hash = await hmacsha256.ComputeHashAsync(request.Body);
request.Body.Position = 0;
var signature = Convert.ToBase64String(hash);
context.HttpContext.Items[ItemsKey] = signature;
await next();
}
}
/// <summary>Register to a controller to compute the HMAC SHA256 signature of the body</summary>
public class BodySignatureFilterAttribute : TypeFilterAttribute
{
/// <summary>Initializes a new instance of <see cref="BodySignatureFilterAttribute"/></summary>
public BodySignatureFilterAttribute()
: base(typeof(BodySignatureFilter)) { }
}