FlipFlip Energy API

Webhooks

Flip sends webhooks to your server when key events occur — demand response events, device commands, enrollment changes, and settings updates. This lets your integration react in real time without polling the API.

Setup

Register your webhook URL in the Flip Developer Console under your webhook settings. Flip will provide a signing key that you'll use to verify incoming requests.

You can subscribe to all event types or filter to specific ones.

Event types

Events

EventDescription
event.createdA new demand response event has been created targeting one of your sites.
event.updatedAn existing event has been updated (e.g. schedule change, participation status).
event.canceledAn event has been canceled.

Commands

EventDescription
command.createdA per-device command has been created for an upcoming event.
command.startedA command has started — your device should begin executing it.
command.endedA command has ended.
command.canceledA command has been canceled before completion.
command.updatedA command has been updated (e.g. status change).

Enrollments

EventDescription
enrollment.createdA site has been enrolled in a program.
enrollment.updatedAn enrollment status has changed (e.g. activated, paused, unenrolled).

Settings

EventDescription
settings.applyDevice settings need to be applied. Read the payload and push the configuration to the device.

Payload structure

Every webhook request body follows the same envelope:

{
  "event_type": "event.created",
  "event_object": { ... },
  "timestamp": "2025-01-15T12:00:00.000Z"
}

The event_object contains the full resource data and includes an object_type field (event, command, enrollment, or settings_request) that identifies which schema applies. See the schema reference pages in the Webhooks section for exact field definitions.

Headers

Every webhook request includes three headers following the Standard Webhooks specification:

HeaderDescription
Webhook-IdUnique identifier for this delivery. Use it to detect and discard duplicates.
Webhook-TimestampUnix timestamp (seconds) when the webhook was created. Reject any request where this value is more than 5 minutes from your server's current time.
Webhook-SignatureHMAC-SHA256 signature for verifying the request came from Flip. Format: v1,<base64-encoded-digest>.

Verifying signatures

To verify a webhook request:

  1. Parse the three headers from the incoming request.
  2. Strip the whsec_ prefix from your signing key and base64-decode the remainder.
  3. Concatenate {Webhook-Id}.{Webhook-Timestamp}.{body} (the raw request body as a string).
  4. Compute an HMAC-SHA256 digest using the decoded key.
  5. Compare your result against the signature in the header.
import { createHmac, timingSafeEqual } from 'node:crypto'

async function verifyWebhook(request: Request, signingKey: string): Promise<boolean> {
  const id = request.headers.get('Webhook-Id')
  const timestamp = request.headers.get('Webhook-Timestamp')
  const signature = request.headers.get('Webhook-Signature')
  if (!id || !timestamp || !signature) return false

  const body = await request.text()

  // Reject stale requests (replay protection)
  const ts = Number(timestamp)
  if (!Number.isFinite(ts)) return false
  const age = Math.abs(Date.now() / 1000 - ts)
  if (age > 300) return false

  const key = Buffer.from(signingKey.replace('whsec_', ''), 'base64')
  const expected = `v1,${createHmac('sha256', key)
    .update(`${id}.${timestamp}.${body}`)
    .digest('base64')}`

  const a = Buffer.from(signature)
  const b = Buffer.from(expected)
  return a.length === b.length && timingSafeEqual(a, b)
}

Best practices

  • Handle duplicates. Track the Webhook-Id of processed deliveries and skip any you've already seen.
  • Respond quickly. Return an HTTP 200 before doing any heavy processing. Use a queue to handle payloads asynchronously.
  • Act on settings.apply promptly. This webhook tells your integration to push settings to the physical device. Delays may affect demand response performance.