Webhooks
EnergyCAP webhooks push real-time notifications to your systems when events occur — bills created, accounts added, exports completed, and more. Instead of polling the API to detect changes, your integration receives an HTTP POST the moment something happens.
Why Webhooks?
Without webhooks, your integration has to poll EnergyCAP repeatedly, checking whether anything has changed. Most of those requests return nothing new, wasting bandwidth and API calls.
| Approach | How it works | Tradeoff |
|---|---|---|
| Polling | Your system calls the API on a schedule | Simple to build, but wasteful and delayed |
| Webhooks | EnergyCAP calls your system when events occur | Real-time, efficient, but requires an endpoint to receive requests |
Webhooks are ideal when you need to react to changes quickly — syncing data to another system, triggering workflows, or updating dashboards.
Supported Events
Each webhook is configured for a single event type. When that event fires, the webhook sends the relevant IDs so your system can look up the full details.
| Event | ID type | Fires when |
|---|---|---|
| Account Create | Account ID | An account is created |
| AP Export | Task ID | Bills are exported to Accounts Payable |
| AP Export Reversal | Task ID | Bills are unexported from AP |
| Batch Closed | Batch ID | A batch is closed (via API, bill import, chargebacks, accruals, or reversal) |
| Bill Created | Bill ID | A bill is created |
| Bill Edited | Bill ID | A bill is edited |
| Bill Analyzed and Reportable | Bill ID | Post-processing completes after bill create/edit (calendarization, normalization, GHG factors, reporting data) |
| Building Create | Place ID | A building is created |
| Custom Account Action | Account ID | A custom action is triggered from the account Actions menu |
| Custom Application Action | — | A custom action is triggered from the application gear icon |
| Custom Bill Action | Bill ID | A custom action is triggered from the bill list |
| Custom Meter Action | Meter ID | A custom action is triggered from the meter Actions menu |
| GL Export | Task ID | Bills are exported to General Ledger |
| GL Export Reversal | Task ID | Bills are unexported from GL |
| Meter Create | Meter ID | A meter is created |
Creating a Webhook
You can manage webhooks in the EnergyCAP UI or via the webhook management APIs.

Each webhook requires:
| Setting | Description |
|---|---|
| Name | A descriptive name for the webhook |
| Description | What the webhook does |
| Event | Which event triggers it (see table above) |
| Target URL | The endpoint that receives the POST request |
| Secret | A shared secret used to generate the ECI-Signature header for signature verification |
| Notification emails | Addresses that receive failure alerts |
Webhooks are active by default and begin firing immediately. You can deactivate them from the Edit Webhook view.
Request Format
Every webhook request is an HTTP POST with the same structure, regardless of event type.
Headers
| Header | Value | Purpose |
|---|---|---|
Content-Type |
application/json; charset=utf-8 |
The payload is always JSON |
ECI-Signature |
HMAC-SHA256 hash | Verifies authenticity and integrity (details below) |
Body
{
"ids": [1995696, 1999706, 2006157],
"eventType": "Bill Created",
"meta": {
"userId": "1024"
}
}
| Field | Type | Description |
|---|---|---|
ids |
int[] |
One or more EnergyCAP IDs relevant to the event. The ID type depends on the event (see Supported Events). |
eventType |
string |
The name of the event that triggered the webhook, e.g. "Bill Created", "AP Export", "Meter Create" |
meta |
object |
Additional context. Currently contains userId — the user who triggered the event. |
The target URL is sent exactly as configured — EnergyCAP does not modify it. You can use query parameters to pass additional context to your receiver:
https://your-system.example.com/webhook?source=energycap&env=production
Signature Verification
Every webhook request includes an ECI-Signature header containing an HMAC-SHA256 hash of the request body. Always verify this signature to confirm the request came from EnergyCAP and hasn’t been tampered with.
How it works
- EnergyCAP removes all whitespace from the JSON body
- Computes an HMAC-SHA256 hash using your webhook’s secret as the key
- Removes dashes between hex bytes and sends the result as the
ECI-Signatureheader
Verification in your code
To verify, compute the same hash on your end and compare:
C#
using System;
using System.Text;
using System.Security.Cryptography;
private bool VerifySignature(string secret, string rawBody, string signature)
{
// Remove all whitespace before hashing — EnergyCAP does the same
var stripped = rawBody
.Replace(Environment.NewLine, "")
.Replace(" ", "");
var secretBytes = Encoding.UTF8.GetBytes(secret);
var bodyBytes = Encoding.UTF8.GetBytes(stripped);
using (var hmac = new HMACSHA256(secretBytes))
{
var hash = hmac.ComputeHash(bodyBytes);
var computed = BitConverter.ToString(hash).Replace("-", "");
return computed == signature;
}
}
Python
import hmac
import hashlib
import re
def verify_signature(secret: str, raw_body: str, signature: str) -> bool:
# Remove all whitespace before hashing
stripped = re.sub(r'\s', '', raw_body)
computed = hmac.new(
secret.encode('utf-8'),
stripped.encode('utf-8'),
hashlib.sha256
).hexdigest().upper()
return hmac.compare_digest(computed, signature)
Node.js
const crypto = require('crypto');
function verifySignature(secret, rawBody, signature) {
// Remove all whitespace before hashing
const stripped = rawBody.replace(/\s/g, '');
const computed = crypto
.createHmac('sha256', secret)
.update(stripped)
.digest('hex')
.toUpperCase();
return computed === signature;
}
Webhook Behavior
Regular webhooks
Most webhooks are asynchronous — they don’t add latency to the API call that triggered them. When a bill is created, the API returns immediately and the webhook fires separately via a background queue.
This means:
- There may be a short delay between the API response and the webhook firing
- Webhooks are not guaranteed to fire in the same order events occurred
- If the webhook fails, it is automatically retried
Retry policy
| Attempt | Timing |
|---|---|
| 1st | Immediate |
| 2nd (retry) | 5 minutes after failure |
| 3rd (retry) | 10 minutes after 2nd failure |
A failure is any response with an HTTP status code outside the 2xx range. After 3 failed attempts, the execution is logged as a failure and a notification email is sent.
Auto-disable: After 10 consecutive failures across any executions, the webhook is automatically disabled and a notification email is sent. You must manually reactivate it in the EnergyCAP UI.
Custom action webhooks
Custom action webhooks (Custom Bill Action, Custom Account Action, Custom Meter Action, Custom Application Action) behave differently:
| Behavior | Regular webhooks | Custom action webhooks |
|---|---|---|
| Timing | Asynchronous (queued) | Synchronous (immediate) |
| Retry on failure | Yes (3 attempts) | No |
| Failure emails | Yes | No |
| UI feedback | None | Success/error banner shown to user |
Custom bill actions are triggered from the bill list in the EnergyCAP UI:
- Select one or more bills
- From the Actions menu, choose the custom action
- Confirm and trigger

Your endpoint can return a custom error message by responding with:
{
"status": {
"message": "Could not process bill ID 1234: missing vendor mapping"
}
}
The message value is displayed directly in the UI error banner. If no structured message is returned, a default error message is shown.
Webhook Log
Monitor webhook activity from the API Keys and Webhooks view. Click the success percentage to open the webhook log.

Each log entry shows:
- Timestamp of the webhook call (local time)
- HTTP status code returned by your endpoint
- Link to view full request/response details

The log detail view shows the complete request headers, request body, response headers, and response body — useful for debugging integration issues.
Development Best Practices
Always verify signatures
Use signature verification to reject fraudulent requests. Without it, anyone who discovers your webhook URL can send fake events.
Return 2xx quickly
Your endpoint should return a 200 or 204 response as fast as possible. Do your processing asynchronously after acknowledging receipt. If your endpoint is slow or returns errors, the webhook will be retried and may eventually be disabled.
# Good: acknowledge immediately, process later
POST /webhook → 200 OK (immediately)
→ queue processing in background
# Bad: do all work before responding
POST /webhook → (30 seconds of processing) → 200 OK
Build for idempotency
The same webhook may fire more than once due to retries. Design your handler so that processing the same event twice produces the same result — for example, check whether you’ve already processed a given bill ID before acting on it.
Watch for chain reactions
If your webhook handler edits a bill in EnergyCAP, and you have a webhook configured for Bill Edited, you’ll trigger another webhook. Guard against infinite loops by tracking which changes originated from your integration.
Handle multiple IDs
The ids array can contain many values, especially for bulk events like AP Export or Custom Bill Actions. Process them in batches rather than making individual API calls for each ID.
Validate the event type
The payload structure is the same for all events, but the ids represent different entity types depending on eventType. Make sure your handler checks the event type and routes to the correct processing logic.
Pass configuration via URL
If your receiver needs webhook-specific settings, pass them as query parameters in the target URL rather than hardcoding them:
https://your-system.example.com/webhook?env=prod&action=sync_bills