Notifications API
RekberPay keeps an in-app notification for every escrow, payment and dispute event that concerns the user. These endpoints read that feed. Workspace notifications for enterprise members are separate; see Enterprise workspace.
Endpoints
| Method | Path | Endpoint | Auth |
|---|---|---|---|
GET | /v1/notifications | List notifications | Key or session |
GET | /v1/notifications/unread-count | Count unread notifications | Key or session |
GET | /v1/notifications/stream | Stream the unread count | Key or session |
PATCH | /v1/notifications/:id/read | Mark one as read | Key or session |
POST | /v1/notifications/mark-all-read | Mark all as read | Key or session |
GET | /v1/notifications/preferences | Get notification settings | Key or session |
PUT | /v1/notifications/preferences | Save notification settings | Key or session |
List notifications
Newest first. body is HTML ready to display; metadata holds IDs such as escrow_id.
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not needed. Reading has no side effects.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
unread_only | string | No | true for unread only |
page | integer | No | Default 1 |
limit | integer | No | 1 to 50. Default 20 |
Example request
curl "https://api.rekberpay.com/v1/notifications?limit=3" \
-H "Authorization: Bearer $REKBERPAY_API_KEY"const res = await fetch('https://api.rekberpay.com/v1/notifications?limit=3', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/notifications?limit=3",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/notifications?limit=3');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": [
{
"id": "ccccccc2-cccc-4ccc-8ccc-cccccccccccc",
"user_id": "11111110-1111-4111-8111-111111111111",
"type": "payment_success",
"title": "Pembayaran Berhasil",
"body": "<p style=\"margin: 0 0 16px 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Aria [shortened]",
"metadata": {
"escrow_id": "55555551-5555-4555-8555-555555555555"
},
"is_read": false,
"created_at": "2026-10-13T14:23:18.818Z",
"total_count": "6"
},
{
"id": "ddddddd2-dddd-4ddd-8ddd-dddddddddddd",
"user_id": "11111110-1111-4111-8111-111111111111",
"type": "escrow_joined",
"title": "Pihak Lain Bergabung",
"body": "<p style=\"margin: 0 0 16px 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Aria [shortened]",
"metadata": {
"escrow_id": "55555551-5555-4555-8555-555555555555",
"amount": 300000
},
"is_read": false,
"created_at": "2026-10-13T14:23:18.802Z",
"total_count": "6"
},
{
"id": "eeeeeee2-eeee-4eee-8eee-eeeeeeeeeeee",
"user_id": "11111110-1111-4111-8111-111111111111",
"type": "escrow_completed_buyer",
"title": "Transaksi Selesai",
"body": "<p style=\"margin: 0 0 16px 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Aria [shortened]",
"metadata": {
"escrow_id": "33333330-3333-4333-8333-333333333333",
"amount": 250000
},
"is_read": false,
"created_at": "2026-10-13T14:23:18.759Z",
"total_count": "6"
}
],
"pagination": {
"page": 1,
"limit": 3,
"total": 6,
"total_pages": 2
}
}Lists are cut to the first 3 items and long text is shortened here.
Errors
| Status | Code | Cause |
|---|---|---|
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Count unread notifications
The number of unread notifications. count comes back as a string of digits, so convert it before comparing.
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not needed. Reading has no side effects.
Example request
curl https://api.rekberpay.com/v1/notifications/unread-count \
-H "Authorization: Bearer $REKBERPAY_API_KEY"const res = await fetch('https://api.rekberpay.com/v1/notifications/unread-count', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/notifications/unread-count",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/notifications/unread-count');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"count": "6"
}
}Errors
| Status | Code | Cause |
|---|---|---|
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Stream the unread count
A Server-Sent Events stream of the unread count, so a bell can update without polling. The response is text/event-stream, not JSON. Each message is one event: line and one data: line of JSON:
retry: 5000first, the reconnect delay in milliseconds.event: unreadwith{"count": n}right after it opens, then again whenever the count changes. Herecountis a number.: pingevery 25 seconds. A comment line, which keeps proxies from closing a quiet stream.event: evictedwith{}when a newer stream of the same user took this one's place. Close the stream and fall back to polling Count unread notifications; do not reconnect.
The server ends every stream after 10 minutes. Reconnect after the retry delay, and you get a fresh count at once. Notifications created by background jobs (auto-release, auto-refund) may only show up at that next reconnect, so keep a slow poll as a backup.
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie. A browserEventSourcesends the cookie when it is created with{ withCredentials: true }. - Rate limit: 30 connection attempts a minute per user. At most 5 open streams per user on each API server; opening another closes the oldest with
event: evicted. - Idempotency: Not needed. Reading has no side effects.
Example request
curl -N https://api.rekberpay.com/v1/notifications/stream \
-H "Authorization: Bearer $REKBERPAY_API_KEY" \
-H "Accept: text/event-stream"const res = await fetch('https://api.rekberpay.com/v1/notifications/stream', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
Accept: 'text/event-stream',
},
});
const decoder = new TextDecoder();
let buffer = '';
for await (const chunk of res.body) {
buffer += decoder.decode(chunk, { stream: true });
let end;
while ((end = buffer.indexOf('\n\n')) !== -1) {
const message = buffer.slice(0, end);
buffer = buffer.slice(end + 2);
const event = /^event: (.+)$/m.exec(message)?.[1];
const data = /^data: (.+)$/m.exec(message)?.[1];
if (event === 'unread') console.log('unread', JSON.parse(data).count);
}
}import json
import os
import requests
with requests.get(
"https://api.rekberpay.com/v1/notifications/stream",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
"Accept": "text/event-stream",
},
stream=True,
timeout=(10, 90),
) as resp:
event = None
for line in resp.iter_lines(decode_unicode=True):
if line.startswith("event: "):
event = line[len("event: "):]
elif line.startswith("data: ") and event == "unread":
print("unread", json.loads(line[len("data: "):])["count"])<?php
$ch = curl_init('https://api.rekberpay.com/v1/notifications/stream');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
'Accept: text/event-stream',
],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) {
foreach (explode("\n", $chunk) as $line) {
if (str_starts_with($line, 'data: ')) {
echo $line, PHP_EOL;
}
}
return strlen($chunk);
},
]);
curl_exec($ch);
curl_close($ch);Example response
200 OK with Content-Type: text/event-stream; charset=utf-8. The stream as it arrives:
retry: 5000
event: unread
data: {"count":6}
: ping
event: unread
data: {"count":7}Errors
Errors come back as JSON before the stream starts.
| Status | Code | Cause |
|---|---|---|
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over 30 connection attempts a minute. Wait for Retry-After seconds |
503 | STREAM_UNAVAILABLE | The server holds too many open streams. Poll the unread count and try again after Retry-After seconds |
Mark one as read
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not supported. A retry runs the call again.
Path parameters
| Name | Type | Description |
|---|---|---|
id | UUID | The notification ID |
Example request
curl -X PATCH https://api.rekberpay.com/v1/notifications/ccccccc2-cccc-4ccc-8ccc-cccccccccccc/read \
-H "Authorization: Bearer $REKBERPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'const res = await fetch('https://api.rekberpay.com/v1/notifications/ccccccc2-cccc-4ccc-8ccc-cccccccccccc/read', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
});
const data = await res.json();import os
import requests
resp = requests.patch(
"https://api.rekberpay.com/v1/notifications/ccccccc2-cccc-4ccc-8ccc-cccccccccccc/read",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
json={},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/notifications/ccccccc2-cccc-4ccc-8ccc-cccccccccccc/read');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(new stdClass()),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"id": "ccccccc2-cccc-4ccc-8ccc-cccccccccccc",
"is_read": true
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No notification with this ID for you |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Mark all as read
marked is how many were unread before the call.
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not supported. A retry runs the call again.
Example request
curl -X POST https://api.rekberpay.com/v1/notifications/mark-all-read \
-H "Authorization: Bearer $REKBERPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'const res = await fetch('https://api.rekberpay.com/v1/notifications/mark-all-read', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/notifications/mark-all-read",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
json={},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/notifications/mark-all-read');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(new stdClass()),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"marked": 5
}
}Errors
| Status | Code | Cause |
|---|---|---|
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Get notification settings
Which kinds of notification the user wants. Every kind is on until it is switched off.
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not needed. Reading has no side effects.
Example request
curl https://api.rekberpay.com/v1/notifications/preferences \
-H "Authorization: Bearer $REKBERPAY_API_KEY"const res = await fetch('https://api.rekberpay.com/v1/notifications/preferences', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/notifications/preferences",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/notifications/preferences');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"escrow_status": true,
"payment_received": true,
"dispute_opened": true,
"promo": true
}
}Errors
| Status | Code | Cause |
|---|---|---|
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Save notification settings
Replaces the settings. Send all four fields.
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not supported. A retry runs the call again.
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
escrow_status | boolean | Yes | Escrow status changes |
payment_received | boolean | Yes | Payments |
dispute_opened | boolean | Yes | Disputes |
promo | boolean | Yes | News and offers |
Example request
curl -X PUT https://api.rekberpay.com/v1/notifications/preferences \
-H "Authorization: Bearer $REKBERPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"escrow_status": true,
"payment_received": true,
"dispute_opened": true,
"promo": false
}'const res = await fetch('https://api.rekberpay.com/v1/notifications/preferences', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
escrow_status: true,
payment_received: true,
dispute_opened: true,
promo: false
}),
});
const data = await res.json();import os
import requests
resp = requests.put(
"https://api.rekberpay.com/v1/notifications/preferences",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
json={
"escrow_status": True,
"payment_received": True,
"dispute_opened": True,
"promo": False,
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/notifications/preferences');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'escrow_status' => true,
'payment_received' => true,
'dispute_opened' => true,
'promo' => false,
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"escrow_status": true,
"payment_received": true,
"dispute_opened": true,
"promo": false
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | A field is missing or not a boolean |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |