FlipFlip Utility API

Webhooks

Flip can push telemetry data to your server as it becomes available, so you don't need to poll the API. When new telemetry is ready, Flip sends an HTTP POST request to your configured endpoint.

Setup

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

Event types

EventDescription
telemetry.publishedSent periodically with aggregated telemetry for devices enrolled in your program. Includes battery, solar, grid, and home power and energy data points.

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.