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
| Event | Description |
|---|---|
telemetry.published | Sent 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:
| Header | Description |
|---|---|
Webhook-Id | Unique identifier for this delivery. Use it to detect and discard duplicates. |
Webhook-Timestamp | Unix 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-Signature | HMAC-SHA256 signature for verifying the request came from Flip. Format: v1,<base64-encoded-digest>. |
Verifying signatures
To verify a webhook request:
- Parse the three headers from the incoming request.
- Strip the
whsec_prefix from your signing key and base64-decode the remainder. - Concatenate
{Webhook-Id}.{Webhook-Timestamp}.{body}(the raw request body as a string). - Compute an HMAC-SHA256 digest using the decoded key.
- 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-Idof processed deliveries and skip any you've already seen. - Respond quickly. Return an HTTP
200before doing any heavy processing. Use a queue to handle payloads asynchronously.