Skip to main content

Webhooks

Flow Access API uses webhooks to notify your system when important events occur in real time.

Instead of polling our system for updates, we push event data directly to your server whenever something happens — such as a meter sending uplinks, a recharge being completed, or water being fetched.

Webhooks are asynchronous and event-driven.


Polling vs Webhooks

Polling

  • Your system repeatedly asks Flow for updates
  • Increased latency and unnecessary requests
  • Not suitable for real-time events

Webhooks

  • Flow sends data only when something happens
  • Near real-time delivery
  • Lower infrastructure and network cost

How Webhooks Work

The webhook delivery flow follows these steps:

  1. Register a Webhook URL When creating or configuring an API client, you provide a webhookURL.

  2. An Event Occurs Examples include:

  • A meter sends uplink data
  • A recharge invoice is created or completed
  • A water fetch is recorded
  1. Flow Sends a Webhook Flow sends an HTTP POST request with a JSON payload to your webhook URL.

  2. Your Server Acknowledges Your server must respond with HTTP 201 Created to confirm successful processing.


Important

If your webhook endpoint:

  • Returns any status code other than 201 Created, or
  • Takes longer than 10 seconds to respond

the webhook delivery is considered failed.


How to Configure Webhooks on the FLOW Platform

To start receiving webhook events, you need to configure your endpoint URLs directly within the FLOW App.

If you are working with an external development or partner team, we highly recommend creating a dedicated Staff Account for them. Assign this account the necessary API permissions so the external team can configure, manage, and test webhook endpoints without needing your primary administrator credentials.

Step 2: Set Up Your Live Webhooks

  1. Log in to your FLOW App .
  2. Navigate to the API Integration tab from the settings tab. Navigate to API Integration
  3. Under the Live environment tab, locate the log type you wish to subscribe to (e.g., Device Logs, Recharges Logs, Fetches Logs).
  4. Enter your destination webhook URL into the input field.
  5. Toggle the Status switch to Enable.
  6. Click the Save (floppy disk) icon to store the URL.

Live Webhook Setup

Step 3: Test Using the Sandbox Environment

Before pushing live data, you should test your data pipeline using our Sandbox to ensure your system parses the data correctly.

  1. Click on the Sandbox sub-tab within the API Integration page.
  2. Enter your testing webhook URL into the relevant field.
  3. Click the Save icon, and ensure the status is enabled.
  4. Click the Simulate Event button. Sandbox Webhook Simulation
  5. The system will dispatch a simulated JSON payload to your endpoint. Check your server logs to verify receipt and ensure your system returns the required response. Sandbox Webhook Simulation

When Webhooks Are Sent

Webhooks are sent automatically when Flow processes events from its internal event bus.

You do not need to call any API to trigger webhooks.

Internally, Flow listens to system events and forwards them to the correct client based on:

  • Business ID
  • Event data type
  • Webhook activation status

Supported Event Types

Each webhook event has a specific purpose and payload structure.


Types of Events

Here are the webhook events currently sent by Flow. We may add more events as we hook into additional actions in the future.

EventDescription
device_logA device (meter) has sent uplinks
recharges_logA recharge, invoice has been created, succeeded, or failed
fetches_logA water fetch transaction has been recorded

This event is sent whenever a meter or device sends uplinks data to Flow.

Payload Structure

{
<<<<<<< HEAD
"battery1Alarm": false,
"battery2Alarm": false,
"eeAlarm": false,
"emptyTubeAlarm": false,
"flowRate": 1.23,
"overRangeAlarm": false,
"pipeLeakageAlarm": false,
"pipeBurstAlarm": false,
"receivedAt": "2025-12-15T10:00:00Z",
"deviceTime": "2025-12-15T09:59:58Z",
"rechargeBalance": 1234.5,
"rechargeTimes": 10,
"reverseFlowAlarm": false,
"address": "687535001098",
"totalConsumption": 54321.9,
"valveStatus": 1,
"waterTemperature": 25.5,
"waterTemperatureAlarm": false,
"valveMode": 1,
"closeThreshold": 100,
"lastPurchaseCredit": 50.0
=======
"battery1Alarm": false,
"battery2Alarm": false,
"eeAlarm": false,
"emptyTubeAlarm": false,
"flowRate": 3.107348472812205,
"overRangeAlarm": false,
"pipeLeakageAlarm": false,
"pipeBurstAlarm": false,
"receivedAt": "2026-02-24T13:16:45.905435186Z",
"deviceTime": "2026-02-24T13:16:45.905435186Z",
"rechargeBalance": 26.561257229488156,
"rechargeTimes": 45,
"reverseFlowAlarm": false,
"address": "GH-MTR-7e91b9",
"totalConsumption": 766.4075148791574,
"valveStatus": 1,
"waterTemperature": 28.416764471084658,
"waterTemperatureAlarm": false,
"valveMode": 1,
"closeThreshold": 0,
"lastPurchaseCredit": 6.802245834762772
>>>>>>> origin/stable
}

Field Descriptions

FieldTypeDescription
battery1AlarmbooleanAlarm status for battery 1.
battery2AlarmbooleanAlarm status for battery 2.
eeAlarmbooleanEEPROM alarm.
emptyTubeAlarmbooleanAlarm for an empty tube.
flowRatenumberCurrent flow rate of water.
overRangeAlarmbooleanAlarm for when the flow is over range.
pipeLeakageAlarmbooleanAlarm for a detected pipe leakage.
pipeBurstAlarmbooleanAlarm for a detected pipe burst.
receivedAtstringTimestamp (ISO 8601) when the server received the data.
deviceTimestringTimestamp (ISO 8601) from the device itself.
rechargeBalancenumberThe current remaining water credit balance.
rechargeTimesintegerThe total number of times the device has been recharged.
reverseFlowAlarmbooleanAlarm for water flowing in the reverse direction.
addressstringThe unique serial number or identifier for the device.
totalConsumptionnumberTotal water consumption recorded by the device.
valveStatusnumberThe current status of the valve.
waterTemperaturenumberThe temperature of the water.
waterTemperatureAlarmbooleanAlarm for water temperature exceeding limits.
valveModeintegerThe operational mode of the valve.
closeThresholdintegerThe threshold at which the valve will close.
lastPurchaseCreditnumberThe amount of the last credit purchase.

Recharge Receipt (recharges_log)

This feed provides information about sales transactions, specifically when a user recharges their water credit balance.

Payload Structure

{
<<<<<<< HEAD
"id": "recharge-id-456",
"amount": 20.0,
"litres": 2000.0,
"pricePerLitre": 0.01,
"currency": "USD",
"paymentMode": "MOBILE_MONEY",
"paymentType": "PREPAID",
"status": "COMPLETED",
"reason": "",
"serialNumber": "687535001098",
"deviceType": "FLOWMETER",
"volume": 2000,
"device": {
"id": "device-id-789",
"name": "Main Street Meter",
"serialNumber": "687535001098",
"status": "ACTIVE",
"category": "FLOWMETER",
"coordinate": {
"latitude": -1.286389,
"longitude": 36.817223
},
"zone": {
"id": "zone-id-abc",
"name": "Downtown",
"location": "Nairobi",
"currency": "KES",
"pricePerLitre": 1.2,
"supportPhoneNumber": "+254700000000"
}
},
"card": {
"id": "card-id-xyz",
"serialNumber": "35001098",
"type": "FLOWMETER",
"linkedAt": "2025-12-15T10:00:00Z",
"createdAt": "2025-12-15T10:00:00Z",
=======
"id": "e93f8747-ddc1-4b55-a24f-032665113dcc",
"amount": 29.44810137103259,
"litres": 196.32067580688394,
"pricePerLitre": 0.15,
"currency": "GHS",
"paymentMode": "AirtelTigo Money",
"paymentType": "Mobile Money",
"status": "COMPLETED",
"serialNumber": "GH-MTR-be5ae4",
"deviceType": "WATER_METER",
"volume": 196,
"device": {
"name": "Simulated Meter Tema",
"serialNumber": "GH-MTR-5f39f3",
"status": "ACTIVE",
"category": "DOMESTIC",
"zone": {
"name": "Eastern Region",
"location": "Tema",
"currency": "GHS",
"pricePerLitre": 0
}
},
"card": {
"serialNumber": "GH-CRD-32728e",
"linkedAt": "2026-02-24T13:18:22.095102037Z",
>>>>>>> origin/stable
"active": true,
"customer": null
},
"user": {
"firstName": "Kwesi",
"lastName": "Osei",
"username": "KwesiOsei",
"phoneNumber": "+233503019855"
},
"performedBy": {
"firstName": "Afua",
"lastName": "Asare",
"username": "AfuaAsare",
"phoneNumber": "+233401276567"
},
"completedAt": "2026-02-24T13:18:22.095102037Z",
"createdAt": "2026-02-24T13:18:22.095102037Z",
"zone": {
"name": "Eastern Region",
"location": "Tema",
"currency": "GHS",
"pricePerLitre": 0.15
},
"type": "credit.topup"
}

Field Descriptions

FieldTypeDescription
idstringThe unique ID for this recharge transaction.
amountnumberThe monetary amount of the recharge.
litresnumberThe number of litres of water credited.
pricePerLitrenumberThe price per litre at the time of the transaction.
currencystringThe currency of the transaction.
paymentModestringHow the payment was made (e.g., "MOBILE_MONEY", "CARD").
paymentTypestringThe type of payment (e.g., "PREPAID").
statusstringThe status of the transaction (e.g., "COMPLETED").
deviceobjectDetails about the device.
cardobjectDetails about the card used.
userobjectThe user who checks the account.
performedByobjectThe user who performed the recharge.
completedAtstringTimestamp (ISO 8601) when the transaction was completed.
createdAtstringTimestamp (ISO 8601) when the transaction was created.
zoneobjectDetails about the zone.
typestringThe type of transaction.

Water Fetch Receipt (fetches_log)

This feed provides a receipt every time water is fetched from a device.

Payload Structure

{
<<<<<<< HEAD
"id": "receipt-id-789",
"type": "FETCH",
"user": {
"id": "user-id-def",
"firstName": "Jane",
"lastName": "Doe",
"username": "janedoe",
"phoneNumber": "+254712345678",
"emailAddress": "jane.doe@example.com"
},
"serialNumber": "687535001098",
"litres": 15.5,
"pricePerLiter": 0.01,
"amount": 0.155,
"createdAt": "2025-12-15T12:00:00Z",
"zone": {
"id": "zone-id-abc",
"name": "Downtown",
"location": "Nairobi",
"currency": "KES",
"pricePerLitre": 1.2,
"supportPhoneNumber": "+254700000000"
},
"device": {
"id": "device-id-789",
"name": "Main Street Meter",
"serialNumber": "device-serial-number-123",
"status": "ACTIVE",
"category": "FLOWMETER",
"coordinate": {
"latitude": -1.286389,
"longitude": 36.817223
},
"zone": {
"id": "zone-id-abc",
"name": "Downtown",
"location": "Nairobi",
"currency": "KES",
"pricePerLitre": 1.2,
"supportPhoneNumber": "+254700000000"
}
},
"card": {
"id": "card-id-xyz",
"serialNumber": "35001098",
"type": "FLOWMETER",
"linkedAt": "2025-12-15T10:00:00Z",
"createdAt": "2025-12-15T10:00:00Z",
=======
"id": "0eaef0da-7747-47d2-94d8-7c8936286798",
"type": "fetch",
"user": {
"firstName": "Ama",
"lastName": "Mensah",
"username": "AmaMensah",
"phoneNumber": "+233315634867"
},
"serialNumber": "GH-MTR-4df53a",
"litres": 56.25507627734599,
"pricePerLiter": 0.15,
"amount": 8.438261441601899,
"createdAt": "2026-02-24T13:19:28.023549625Z",
"zone": {
"name": "Northern Region",
"location": "Obuasi",
"currency": "GHS",
"pricePerLitre": 0.15
},
"device": {
"name": "Simulated Meter Obuasi",
"serialNumber": "GH-MTR-54a4d8",
"status": "ACTIVE",
"category": "DOMESTIC",
"zone": {
"name": "Northern Region",
"location": "Obuasi",
"currency": "GHS",
"pricePerLitre": 0
}
},
"card": {
"serialNumber": "GH-CRD-915ccc",
"linkedAt": "2026-02-24T13:19:28.023549625Z",
>>>>>>> origin/stable
"active": true,
"customer": null
},
"balance": 21.361432465341508,
"businessId": "bbae9a2a-90e1-4da2-b012-cb6a46011b92"
}

Field Descriptions

FieldTypeDescription
idstringThe unique ID for this water fetch receipt.
typestringThe type of event (e.g., "FETCH").
userobjectThe user who fetched the water.
serialNumberstringThe serial number of the device.
litresnumberThe amount of water fetched in litres.
pricePerLiternumberThe price per litre at the time of fetching.
amountnumberThe total cost of the water fetched.
createdAtstringTimestamp (ISO 8601) when the water was fetched.
zoneobjectThe zone where the fetch occurred.
deviceobjectThe device used for fetching.
cardobjectThe card used for fetching.
balancenumberThe remaining balance on the account.
businessIdstringThe ID of the business associated with this transaction.

Handling Webhooks Correctly

Your webhook endpoint should:

  1. Accept POST requests
  2. Parse JSON payloads
  3. Process the event idempotently
  4. Respond within 10 seconds

Example Integration

func HandleWebhook(c echo.Context) error {
// ... (see api.md for full struct definitions)
return c.NoContent(http.StatusCreated)
}