merchant.balance.waterline
Aggregate downstream merchant funding-pool level changed
When Emitted
Sent to downstream merchants when the available funds of all real downstream merchants, aggregated by asset, cross an alert, pause, or resume threshold. scope is always all_downstream_merchants. Sandbox and platform-direct funds are excluded; aggregate amounts are visible only in the platform administration console. halted=true pauses new card top-ups, card withdrawals, and withdrawals; merchant funding and in-flight settlements continue. halted=false only means that this asset’s funding level is not paused. Pauses for any other asset, manual restrictions, and risk-control restrictions still apply. Retries of the same change reuse event_id; an older event arriving later must not overwrite a newer status_version.
Triggered by a scheduled job or a provider callback.
Payload
{
"event_id": "evt_11111111222243338444555555555555",
"event_type": "merchant.balance.waterline",
"created_at": "2026-09-09T08:00:00Z",
"merchant_id": "acme",
"livemode": true,
"data": {
"object": "merchant_balance",
"id": "USD",
"external_member_id": "",
"status": "halt",
"status_version": 1788940800000,
"asset": "USD",
"scope": "all_downstream_merchants",
"halted": true
}
}
Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Use this for deduplication. evt_… remains unchanged when the same event is redelivered. |
type | string | Always merchant.balance.waterline |
created_at | string | Time the event was created (RFC3339), not its delivery time. It is unchanged on redelivery. |
data.object | string | Object type; determines which endpoint to query with data.id |
data.id | string | Object ID; use it to retrieve details. |
data.status | string | Treat unrecognized values as unknown and raise an alert; do not fall back to “processing” |
data.status_version | number | Monotonically increasing; use it to discard older states that arrive late. |
Signature Verification and Deduplication
Verify the signature against the raw request body bytes. Do not parse and reserialize the JSON: your JSON library may change key order or whitespace, which changes the signature and can look like a key configuration error.
// Node · Run before parsing JSON
const raw = await readRawBody(req); // Buffer / string; do not use parsed req.body
const expect = crypto.createHmac("sha256", WEBHOOK_SECRET).update(raw).digest("hex");
const got = req.headers["z-signature"]; // Format: t=<unix>,v1=<hex>
if (!timingSafeEqual(expect, parseV1(got))) return res.status(400).end();
// Deduplicate using the envelope id, not data.id
if (await seen(JSON.parse(raw).id)) return res.status(200).end();
For the full procedure, including timestamp tolerance and redelivery semantics, see Webhook Guide.