Webhooks are in beta. The payload format and signing scheme may change.
transactions.synced events; brokerage syncs produce trades.synced events. Every request is signed with HMAC-SHA256.
Event types
The envelope shape is identical across event types — only the
type field and the contents of data.new / data.updated change. Consumers that only care about one event type should filter on event.type and ignore the rest.
Request headers
Each webhook request includes these headers:Payload format
All field names use snake_case. Monetary amounts on transactions are integers in the smallest currency unit (e.g. cents). Envelope timestamps (created) are Unix epoch seconds. The local_date field on transactions is a YYYY-MM-DD string; transaction_date and post_date are full ISO 8601 timestamps passed through from the banking provider. Missing optional fields are null, never empty strings.
Top-level fields
Transaction fields
Each transaction indata.new contains:
Amounts are integers in the smallest currency unit. For example,
$45.50 AUD is 4550. Zero-decimal currencies like JPY use the full amount (e.g. ¥500 is 500).Updated transaction fields
Each entry indata.updated contains all the same fields above, plus:
data.updated is populated when a sync detects mutations to previously-delivered transactions (for example, the banking provider’s stored values for a posted transaction change between syncs). The full current attributes are sent on every entry; you can compare against your own stored copy to derive the diff while previous_attributes remains reserved.
Trade fields
Trade events use the same envelope as transactions, but withtype: "trades.synced" and Trade objects in data.new. Quantities, prices, and fees are decimal strings (not integers) to preserve precision for fractional shares. data.updated is reserved for future use and currently always empty.
data.new contains:
Trade amounts are decimal strings, not integers. This is different from the transaction payload (which uses smallest-unit integers) because brokerage quantities can be fractional — parsing as a
Decimal/BigDecimal on your side is recommended.Metadata fields
Chunking
When a sync produces more than 500 transactions, the payload is split into multiple requests. Each chunk is delivered sequentially as a separate POST. Usemetadata.chunk and metadata.total_chunks to reassemble if needed.
Verifying signatures
Every webhook request is signed with HMAC-SHA256 using your signing secret. Verify the signature on every request to confirm it came from Redbark and has not been tampered with.Verification steps
- Extract the
X-Redbark-TimestampandX-Redbark-Signatureheaders - Concatenate the timestamp and raw request body with a period:
<timestamp>.<body> - Compute the HMAC-SHA256 digest of this string using your signing secret
- Compare your computed signature with the one in the header (strip the
sha256=prefix) - Check that the timestamp is within 5 minutes of the current time to prevent replay attacks
Example: Node.js
Example: Python
Expected response
Your endpoint must return a 2xx status code (e.g.200 OK) to acknowledge receipt. The response body is ignored.
Retry schedule
Each delivery makes up to 3 attempts in total. Sleeps before each retry use exponential backoff with a base of 3:
The per-attempt request timeout is 30 seconds.
Auto-disable on failed deliveries
A single failed delivery cycle (all 3 attempts for the same payload exhausted, or a non-retried 4xx) increments the destination’sconsecutiveFailures counter. Once the counter reaches 1, the destination is automatically disabled (disabledAt is set) and no further deliveries are attempted until the destination is manually re-enabled from the dashboard. The successful delivery of any subsequent payload resets the counter to zero. Auto-disable is intentionally aggressive: webhook endpoints are integrator-controlled, and continuing to retry against a broken endpoint causes more harm than good.
Timeouts are the exception. A timed-out chunk is recorded as a failed delivery, but it does not increment consecutiveFailures, does not disable the destination, and does not halt delivery of the remaining chunks in the sync.
Deduplication
Entity IDs (transaction IDs and trade IDs) that have been successfully delivered are tracked per destination. Each successful delivery upserts the row, refreshingdeliveredAt — the 90-day retention window therefore runs from the last successful delivery of that entity, not the first. If an entity is still inside its window when the next sync runs, it will not appear in data.new. Transactions and trades are deduplicated independently — a trade ID that happens to match a transaction ID won’t collide. Your endpoint should still handle potential duplicates gracefully (e.g. using <event.type>:<object.id> as an idempotency key) in case of network-level retries.