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.

Create webhook

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

  1. EnergyCAP removes all whitespace from the JSON body
  2. Computes an HMAC-SHA256 hash using your webhook’s secret as the key
  3. Removes dashes between hex bytes and sends the result as the ECI-Signature header

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;
}
Tip
If your computed hash doesn’t match, the most common cause is not stripping whitespace from the body before hashing. EnergyCAP removes all whitespace (spaces, newlines, tabs) before computing the 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:

  1. Select one or more bills
  2. From the Actions menu, choose the custom action
  3. Confirm and trigger

Custom bill action

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.

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

Webhook log details

The log detail view shows the complete request headers, request body, response headers, and response body — useful for debugging integration issues.

Tip
Webhook logs are retained for 3 days or 100 entries, whichever is greater. If you need longer retention, log webhook requests on your receiving end.

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