Dispatch Webhooks
This guide takes you through the steps and considerations when setting up dispatch event push notifications from Leap to your webhook endpoint. Leveraging this webhook push method is recommended in order to get dispatch notifications as soon as they become available, enabling participation in real-time programs that provide minimal advance notice periods.
1. Setup Your Webhook Endpoint
This webhook endpoint should be reachable on the Internet and accept HTTPS POST requests from Leap on port 443. If you don't yet have an external URL for your endpoint, you can use a free tool like webhook.site or Mockbin for testing.
2. Set Your Webhook URL in Leap
Use the meter-level set webhook URL endpoint or group-level set group webhook URL endpoint to configure the URL that Leap will use to post dispatch notifications to.
{
"headers": [
{
"name": "x-api-key",
"value": "your-test-key"
}
],
"url": "https://webhook.site/4a0a9a89-d571-4ab7-ae8c-f5c864a3e5d1"
}When you first create a webhook, the response contains a signing_secret:
{
"headers": [
{
"name": "x-api-key",
"value": "your-test-key"
}
],
"url": "https://webhook.site/4a0a9a89-d571-4ab7-ae8c-f5c864a3e5d1",
"signing_secret": "whsec_pSz3guN3Bvj-B1CamLeVpIpLiBTA-y0rw6XBgmxBznk"
}Signing Secret
Leap uses the signing secret to sign every notification it sends to your webhook. The signature is in the x-leap-signature header. To verify incoming requests are from Leap, store the secret securely, for example in a secret manager, because your endpoint needs it to verify that a notification comes from Leap. Section 4 below, Verify the Leap Signature, explains how.
The signing_secret is only returned:
- When you create a webhook. Each webhook (meter-level and group-level) has its own secret.
- When you update a webhook with
rotate_secret=true, for examplePUT /v2/dispatch/meter/webhook?rotate_secret=true. This generates a new secret, which replaces the old one immediately.
Updating a webhook without rotate_secret=true keeps the current secret and does not return it. Leap cannot show a secret again; if you lose it, rotate it. Webhooks created before signing was introduced have no secret and are sent without the header until you rotate.
Webhook SecurityVerifying the
x-leap-signatureheader is the recommended way to authenticate notifications from Leap (see section 4). You can also include an API key or authentication token in theheadersarray, for example to reject requests early at your API gateway.Note: all header names, including
x-leap-signature, are sent in lowercase, regardless of how they were configured in theheadersarray.
3. Send Your Endpoint a Test Notification
You can use the provided trigger test notification endpoints (meter-level | group-level) to send your webhook endpoint a test notification. These test endpoints allow you to define all of the values (meter/group IDs and timeslot objects) to be used for dispatch events within a notification message. Review the tables within the Dispatch Event & Program Prioritization guide for the priority, performance_compensation_cap, and is_voluntary values for each program you would like to setup test events for.
With these test endpoints you can simulate the different types of dispatches you could receive. Review the Test Cases guide for the different scenarios to test.
Multiple EventsWith these test notification endpoints, it is possible to send yourself test notifications with more than one timeslot within the
timeslotsarray or more than one meter/group ID-to-timeslots combination within themeter_dispatchesarray as could happen with a real market/program event. See the push notifications shown at the bottom of this guide for an example of this.
Notification InfrastructureThese trigger test notification endpoints use the same service and infrastructure (e.g. source IPs) as actual market/program events. The only difference is the
integration_test_notificationflag in the post body being set to true, indicating that it was initiated through one of these test endpoints.
Retries on Timeouts & ErrorsTo support real-time programs and provide notifications as quickly as possible, retries in the case of timeouts or errors happen every 1 minute for up to 50 minutes (50 max retries).
4. Optionally, Verify the Leap Signature
Leap signs every notification with a Stripe-style, timestamped HMAC-SHA256 signature. Use it to check that the notification comes from Leap, the body was not changed in transit, and it is not an old notification being replayed. The timestamp is part of the signature, so it cannot be changed.
How Leap signs a notification
- Leap serializes the notification to bytes once and sends exactly those bytes as the request body. The signature is calculated over those same bytes.
timestamp= the current time in Unix seconds.signed_payload={timestamp}.{body}v1= hex(HMAC-SHA256(signing_secret, signed_payload)). The key is the full signing secret, including thewhsec_prefix, as UTF-8 bytes. Do not base64-decode it.- Leap adds the header:
x-leap-signature: t=1759658400,v1=93fd212342b16ef77bd0f5630a1ca5fa02972fedef7bbf8962cd54634853a08f
How to verify a notification
- Read the raw request body before parsing the JSON. If you parse the JSON and serialize it again, whitespace or field order may change and the signature won't match.
- Split the
x-leap-signatureheader on,and each part on the first=. Taketand thev1value(s), and ignore unknown keys. A future signature scheme may add, for example, av2. - Compute HMAC-SHA256 of
{t}.{raw body}with your signing secret. - Compare the result with
v1using a constant-time comparison. - Reject the notification if
tis more than 5 minutes from your current time. Keep your server clock in sync using NTP.
If verification fails, respond with 401 and don't process the notification.
Every retry is signed again with a new t, so retried notifications pass the timestamp check. Notifications sent with the test endpoints in section 3 are signed in the same way, so use them to test your implementation.
Rotating the secret. A rotated secret is used immediately. Notifications sent before your endpoint uses the new secret fail verification. You can accept both the old and the new secret for a short time. Alternatively, rely on the retries: once you use the new secret, retried notifications pass verification as long as they're still within the 50-minute retry window.
Test vector. Use these values to check your implementation:
| Secret | whsec_pSz3guN3Bvj-B1CamLeVpIpLiBTA-y0rw6XBgmxBznk |
| Timestamp | 1759658400 |
| Body | {"event":"test"} |
| Expected header | t=1759658400,v1=93fd212342b16ef77bd0f5630a1ca5fa02972fedef7bbf8962cd54634853a08f |
5. Process Incoming Notifications
The Leap service sending the push notifications is expecting a 2XX HTTP response back for it to be considered a successfully delivered notification. The service currently retries any notifications that failed due to timeouts or non-2XX responses every 1 minute (for up to 50 minutes).
Endpoint MonitoringSetting up monitoring/alerting is recommended in order to ensure your webhook endpoint remains up and available as well as to check for any non-2XX responses back to Leap.
Webhook Processing Considerations:
- Each webhook push notification includes a
integration_test_notificationfield. This will betruefor all notifications triggered by these test endpoints andfalsefor all real dispatch notifications. - If your system successfully receives the notification but the acknowledgement response back to Leap gets dropped and the request times out, the two sides could be out of sync causing Leap to retry with another push notification which your system will consider a duplicate of the previous. Ensure your implementation can handle this corner case gracefully.
For additional processing considerations refer back to the Dispatch Event Processing and Dispatch Strategies sections of the Dispatch Automation guide.
Example Code Recipe
Check out the following example code recipe for processing these webhook push notifications:
Meter Notification Example
{
"integration_test_notification": true,
"communication_test": false,
"meter_dispatches": [
{
"meter_id": "dd21c76c-7126-4ef7-b0a6-f190fad300b2",
"timeslots": [
{
"meter_event_id": "11cfced0-16f4-4fb7-b72e-5ff939ae541e",
"start_time": "2023-06-05T00:00:00Z",
"end_time": "2023-06-05T01:00:00Z",
"cancelled": false,
"performance_compensation_cap": "up-to-site-load",
"priority": 1,
"is_voluntary": false,
"dispatch_event_types": [
"day-ahead",
"capacity-test"
],
"programs": [
"CCA"
],
"energy_kw": 10.000,
"nomination_kw": 10.000
},
{
"meter_event_id": "22cfced0-16f4-4fb7-b72e-5ff939ae541e",
"start_time": "2023-06-05T01:00:00Z",
"end_time": "2023-06-05T02:00:00Z",
"cancelled": false,
"performance_compensation_cap": "up-to-site-load",
"priority": 1,
"is_voluntary": false,
"dispatch_event_types": [
"day-ahead",
"capacity-test"
],
"programs": [
"CCA"
],
"energy_kw": 20.000,
"nomination_kw": 20.000
}
]
},
{
"meter_id": "dd21c76c-7126-4ef7-b0a6-f190fad300b2",
"timeslots": [
{
"meter_event_id": "33cfced0-16f4-4fb7-b72e-5ff939ae541e",
"start_time": "2023-06-05T01:00:00Z",
"end_time": "2023-06-05T05:00:00Z",
"cancelled": false,
"performance_compensation_cap": "grid-exports-allowed",
"priority": 3,
"is_voluntary": true,
"dispatch_event_types": [
"standard"
],
"programs": [
"ELRP"
],
"energy_kw": null,
"nomination_kw": 4.500
}
]
}
]
}
Market Group Notification Example
{
"integration_test_notification": true,
"communication_test": false,
"group_dispatches": [
{
"market_group_id": "11aa7aae-24e5-455f-91e9-e0f5f145f2ae",
"meter_ids": [
"abae2779-e392-4f00-b0cf-32e716204f78",
"bbae2779-e392-4f00-b0cf-32e716204f78"
],
"timeslots": [
{
"market_group_event_id": "114751e8-c0df-4d69-8a4f-9e7cebef1f84",
"start_time": "2023-06-04T23:00:00Z",
"end_time": "2023-06-05T00:00:00Z",
"cancelled": false,
"performance_compensation_cap": "up-to-site-load",
"priority": 2,
"is_voluntary": false,
"dispatch_event_types": [
"day-ahead"
],
"programs": [
"CCA"
],
"energy_kw": 8.500,
"nomination_kw": 11.000
},
{
"market_group_event_id": "224751e8-c0df-4d69-8a4f-9e7cebef1f84",
"start_time": "2023-06-05T00:00:00Z",
"end_time": "2023-06-05T03:00:00Z",
"cancelled": false,
"performance_compensation_cap": "grid-exports-allowed",
"priority": 3,
"is_voluntary": true,
"dispatch_event_types": [
"standard"
],
"programs": [
"ELRP"
],
"energy_kw": null,
"nomination_kw": 11.000
}
]
},
{
"market_group_id": "331a7aae-24e5-455f-91e9-e0f5f145f2ae",
"meter_ids": [
"ccae2779-e392-4f00-b0cf-32e716204f78",
"ddae2779-e392-4f00-b0cf-32e716204f78"
],
"timeslots": [
{
"market_group_event_id": "334751e8-c0df-4d69-8a4f-9e7cebef1f84",
"start_time": "2023-06-05T00:00:00Z",
"end_time": "2023-06-05T04:00:00Z",
"cancelled": false,
"performance_compensation_cap": "up-to-site-load",
"priority": 2,
"is_voluntary": false,
"dispatch_event_types": [
"hour-ahead",
"day-ahead",
"capacity-test"
],
"programs": [
"CCA"
],
"energy_kw": 12.000,
"nomination_kw": 12.000
}
]
}
]
}
Updated 17 days ago