Polling events
Every change in a workspace is written to an append-only feed. Each event has a seq that only goes up. To stay in step, remember the highest seq you processed and ask for everything after it. This works with any workspace key, needs no public endpoint, and fills any gap a webhook outage left.
1. Find where the feed is now
The first time, read the newest events to learn the current position:
curl "https://api.rekberpay.com/v1/enterprise/events?order=desc&limit=3" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/events?order=desc&limit=3', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/events?order=desc&limit=3",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/events?order=desc&limit=3');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);200 OK
{
"success": true,
"data": {
"items": [
{
"seq": 140566,
"id": "ddddddd7-dddd-4ddd-8ddd-dddddddddddd",
"event": "disbursement.queued",
"entity_type": "disbursement",
"entity_id": "ccccccc7-cccc-4ccc-8ccc-cccccccccccc",
"deal_id": "44444447-4444-4444-8444-444444444444",
"deal_reference": "DEAL-2026-0011",
"actor": {
"id": "33333333-3333-4333-8333-333333333333",
"name": "Dimas Hidayat"
},
"actor_label": null,
"amount": 150000000,
"summary": "Payout queued for DEAL-2026-0011 / Moulds delivered and inspected",
"created_at": "2026-10-13T14:33:16.635Z",
"livemode": false
},
{
"seq": 140565,
"id": "eeeeeee7-eeee-4eee-8eee-eeeeeeeeeeee",
"event": "milestone.released",
"entity_type": "milestone",
"entity_id": "55555557-5555-4555-8555-555555555555",
"deal_id": "44444447-4444-4444-8444-444444444444",
"deal_reference": "DEAL-2026-0011",
"actor": {
"id": "33333333-3333-4333-8333-333333333333",
"name": "Dimas Hidayat"
},
"actor_label": null,
"amount": 150000000,
"summary": "Released DEAL-2026-0011 / Moulds delivered and inspected in 1 payout",
"created_at": "2026-10-13T14:33:16.634Z",
"livemode": false
}
],
"next_before": 140564,
"next_after": 140566
}
}Lists are cut to the first 2 items and long text is shortened here.
Store next_after. It is the highest seq on the page. To read older history instead, pass next_before as before with order=desc.
2. Poll from the last seq
curl "https://api.rekberpay.com/v1/enterprise/events?after=140563&limit=3" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/events?after=140563&limit=3', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/events?after=140563&limit=3",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/events?after=140563&limit=3');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);200 OK
{
"success": true,
"data": {
"items": [
{
"seq": 140564,
"id": "fffffff7-ffff-4fff-8fff-ffffffffffff",
"event": "milestone.approved",
"entity_type": "milestone",
"entity_id": "55555557-5555-4555-8555-555555555555",
"deal_id": "44444447-4444-4444-8444-444444444444",
"deal_reference": "DEAL-2026-0011",
"actor": {
"id": "77777776-7777-4777-8777-777777777777",
"name": "Maya Santoso"
},
"actor_label": null,
"amount": 150000000,
"summary": "Approved DEAL-2026-0011 / Moulds delivered and inspected for release",
"created_at": "2026-10-13T14:33:16.626Z",
"livemode": false
},
{
"seq": 140565,
"id": "eeeeeee7-eeee-4eee-8eee-eeeeeeeeeeee",
"event": "milestone.released",
"entity_type": "milestone",
"entity_id": "55555557-5555-4555-8555-555555555555",
"deal_id": "44444447-4444-4444-8444-444444444444",
"deal_reference": "DEAL-2026-0011",
"actor": {
"id": "33333333-3333-4333-8333-333333333333",
"name": "Dimas Hidayat"
},
"actor_label": null,
"amount": 150000000,
"summary": "Released DEAL-2026-0011 / Moulds delivered and inspected in 1 payout",
"created_at": "2026-10-13T14:33:16.634Z",
"livemode": false
}
],
"next_after": 140566
}
}Lists are cut to the first 2 items and long text is shortened here.
Process the items in order, then store next_after and send it as after on the next poll. When nothing is new, items is empty and next_after repeats your after. If a page comes back full (limit items), poll again at once.
3. Filter
Narrow the feed to one deal and one event name:
curl "https://api.rekberpay.com/v1/enterprise/events?deal_id=eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee&event=milestone.released&limit=5" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/events?deal_id=eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee&event=milestone.released&limit=5', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/events?deal_id=eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee&event=milestone.released&limit=5",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/events?deal_id=eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee&event=milestone.released&limit=5');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);200 OK
{
"success": true,
"data": {
"items": [
{
"seq": 140544,
"id": "11111118-1111-4111-8111-111111111111",
"event": "milestone.released",
"entity_type": "milestone",
"entity_id": "fffffff4-ffff-4fff-8fff-ffffffffffff",
"deal_id": "eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee",
"deal_reference": "DEAL-2026-0009",
"actor": {
"id": "33333333-3333-4333-8333-333333333333",
"name": "Dimas Hidayat"
},
"actor_label": null,
"amount": 30000000,
"summary": "Released DEAL-2026-0009 / Tooling and proof approval in 2 payouts",
"created_at": "2026-10-13T14:33:16.481Z",
"livemode": false
}
],
"next_after": 140544
}
}A polling loop
const BASE = 'https://api.rekberpay.com/v1/enterprise/events';
async function pollOnce(lastSeq, apply) {
let after = lastSeq;
for (;;) {
const res = await fetch(`${BASE}?after=${after}&limit=100`, {
headers: { Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}` },
});
if (res.status === 429) {
const wait = Number(res.headers.get('retry-after') ?? 30);
await new Promise((r) => setTimeout(r, wait * 1000));
continue;
}
const { data } = await res.json();
for (const event of data.items) await apply(event);
after = data.next_after;
if (data.items.length < 100) return after;
}
}
// Store the returned seq after each run, then call it again in 15 to 60 seconds.Tips
- Every 15 to 60 seconds is plenty. Each poll counts against the shared budget of 300 requests a minute.
- Save the new
seqin the same database transaction as the changes you made from the events, so a crash never skips or repeats work. - The feed is the same data webhooks send. Using both is fine: deduplicate on the event
id. - Events are notifications. When you need the full current state, read it, for example with
GET /v1/enterprise/deals/:id.