Receive real-time HTTP notifications when payments, payouts, disputes, and other resources change state. Watenga delivers a signed JSON event to your endpoint.
Configuration
Developer Dashboard → API App → Webhooks — add your HTTPS URL and select the events you want to receive. Each app has its own webhook secret used to sign every delivery.
New apps subscribe to payment.capture.completed and payment.capture.declined by default. Subscribe to * to receive every event.
The event object
Every webhook body is an event envelope. The type is a canonical registry id (e.g. payment.capture.completed), and the resource lives under data.
The signed payload is the timestamp and the raw request body joined by a dot: `${t}.${rawBody}`. Recompute the HMAC-SHA256 with your webhook secret and compare it to v1 using a constant-time comparison.
Verify before parsing
Verify the signature against the raw body before parsing JSON or touching your database. Reject events whose timestamp is more than 5 minutes (300s) old to defeat replay attacks, and always use a constant-time comparison such as crypto.timingSafeEqual.
Node.js
javascript
const crypto = require('crypto');
// Watenga signs every delivery with the 'Watenga-Signature' header:
// t=<unix-seconds>,v1=<hmac-sha256-hex>
// where v1 = HMAC_SHA256(secret, `${t}.${rawBody}`)
const header = req.headers['watenga-signature'] || '';
const parts = Object.fromEntries(
header.split(',').map((kv) => kv.split('=').map((s) => s.trim())),
);
const timestamp = Number(parts.t);
const signature = parts.v1 || '';
// Reject replays older than 5 minutes (300s tolerance).
if (!Number.isFinite(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > 300) {
throw new Error('Webhook timestamp outside tolerance window');
}
const expected = crypto
.createHmac('sha256', process.env.WATENGA_WEBHOOK_SECRET)
.update(`${timestamp}.${req.rawBody}`, 'utf8')
.digest('hex');
const a = Buffer.from(signature);
const b = Buffer.from(expected);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
throw new Error('Invalid signature');
}
// Envelope: { id, object: 'event', type, created, data }
const event = JSON.parse(req.rawBody);
if (event.type === 'payment.capture.completed') {
// fulfil the order — event.data holds the transaction
}
The official SDKs verify for you via Webhooks.constructEvent() — see the Node.js and Go / Flutter helpers.
Retry policy
Respond with a 2xx status within 15 seconds to acknowledge a delivery. Any other status (or a timeout) is retried up to 5 attempts with exponential backoff starting at 2 seconds. Failed deliveries are visible — and replayable — in the Webhook Events log of your Developer Dashboard.
Events emitted today
These events are currently delivered by Watenga. The full registry — including events reserved for upcoming features — is available from GET /v1/meta/webhook-events and the catalogue below.
Event
Description
payment.capture.completed
Card or wallet capture succeeded
split.settled
Net payment share credited to a subaccount
payment.refund.completed
Refund credited to customer
payment.refund.pending
Refund awaiting gateway confirmation
payment.payout-item.succeeded
Single payout item completed
customer.dispute.created
Chargeback or dispute opened
merchant.onboarding.submitted
Sub-merchant submitted KYC for review
merchant.onboarding.kyc-approved
Sub-merchant KYC approved
billing.invoice.created
Watenga platform subscription invoice generated
capital.offer_created
Merchant has a new Watenga Capital advance offer
capital.disbursed
Advance funds credited to merchant balance
capital.repayment
Holdback deducted from settlement
capital.repaid
Advance fully repaid
Full event catalogue
Generated from the canonical webhook event registry in @watenga/shared (mirrored to content/webhook-events.generated.md). “Live” events are delivered today; others are reserved for upcoming features.