Webhooks

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.

json
{
  "id": "evt_550e8400-e29b-41d4-a716-446655440000",
  "object": "event",
  "type": "payment.capture.completed",
  "created": 1717406400,
  "data": {
    "id": "txn_01HQXYZ",
    "amount": "25.0000",
    "net": "24.1300",
    "currency": "USD",
    "status": "completed"
  }
}
FieldTypeDescription
idstringUnique event id, prefixed evt_
objectstringAlways “event”
typestringCanonical event type from the registry
creatednumberUnix timestamp (seconds) the event was created
dataobjectThe resource that triggered the event

Signature verification

Each delivery carries a Watenga-Signature header. It contains a Unix timestamp and an HMAC-SHA256 signature, comma-separated:

plain
Watenga-Signature: t=1717406400,v1=5257a869e7ec3ead4f1b0f8b5b1f8b6f9f0c2e1d3a4b5c6d7e8f9a0b1c2d3e4f

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
}

PHP

php
$header = $_SERVER['HTTP_WATENGA_SIGNATURE'] ?? '';
$parts = [];
foreach (explode(',', $header) as $segment) {
    [$k, $v] = array_pad(explode('=', $segment, 2), 2, '');
    $parts[trim($k)] = trim($v);
}
$timestamp = (int) ($parts['t'] ?? 0);
$signature = $parts['v1'] ?? '';

// Reject replays older than 5 minutes (300s tolerance).
if (abs(time() - $timestamp) > 300) {
    http_response_code(400);
    exit('Timestamp outside tolerance window');
}

$rawBody = file_get_contents('php://input');
$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, getenv('WATENGA_WEBHOOK_SECRET'));

if (!hash_equals($expected, $signature)) {
    http_response_code(400);
    exit('Invalid signature');
}

$event = json_decode($rawBody, true);
// $event['type'] === 'payment.capture.completed'

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.

EventDescription
payment.capture.completedCard or wallet capture succeeded
split.settledNet payment share credited to a subaccount
payment.refund.completedRefund credited to customer
payment.refund.pendingRefund awaiting gateway confirmation
payment.payout-item.succeededSingle payout item completed
customer.dispute.createdChargeback or dispute opened
merchant.onboarding.submittedSub-merchant submitted KYC for review
merchant.onboarding.kyc-approvedSub-merchant KYC approved
billing.invoice.createdWatenga platform subscription invoice generated
capital.offer_createdMerchant has a new Watenga Capital advance offer
capital.disbursedAdvance funds credited to merchant balance
capital.repaymentHoldback deducted from settlement
capital.repaidAdvance 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.

PAYMENT_CAPTURE

EventDescriptionStatus
payment.capture.completedCard or wallet capture succeededLive
payment.capture.declinedCapture failed after authorizationPlanned
payment.capture.pendingAwaiting customer actionPlanned
payment.capture.refundedCapture fully or partially refundedPlanned
payment.capture.reversedAcquirer reversal before settlementPlanned
payment.capture.expiredCheckout session expired without paymentPlanned
payment.capture.authorizedPayment authorized awaiting capturePlanned
payment.capture.cancelledCustomer cancelled checkoutPlanned

SPLIT

EventDescriptionStatus
split.settledNet payment share credited to a subaccountLive

PAYMENT_AUTHORIZATION

EventDescriptionStatus
payment.authorization.createdFunds authorized on cardPlanned
payment.authorization.voidedAuthorization voided before capturePlanned

PAYMENT_REFUND

EventDescriptionStatus
payment.refund.completedRefund credited to customerLive
payment.refund.failedRefund could not be processedPlanned
payment.refund.cancelledRefund was cancelledPlanned
payment.refund.pendingRefund awaiting gateway confirmationLive
payment.refund.createdRefund record createdPlanned

PAYMENT_ORDER

EventDescriptionStatus
payment.order.createdPayment order createdPlanned
payment.order.cancelledPayment order cancelledPlanned
payment.order.approvedPayment order approvedPlanned
payment.order.voidedPayment order voidedPlanned

PAYOUT

EventDescriptionStatus
payment.payout-item.succeededSingle payout item completedLive
payment.payout-item.failedPayout item failedPlanned
payment.payout-item.blockedPayout blocked by riskPlanned
payment.payout-item.heldPayout held for reviewPlanned
payment.payout-item.returnedPayout returned by bankPlanned
payment.payout-item.unclaimedPayout unclaimed by recipientPlanned
payment.payout-batch.successPayout batch completedPlanned
payment.payout-batch.processingPayout batch processingPlanned
payment.payout-batch.deniedPayout batch deniedPlanned

INVOICE

EventDescriptionStatus
invoicing.invoice.paidInvoice marked paidPlanned
invoicing.invoice.cancelledInvoice cancelledPlanned
invoicing.invoice.createdNew invoice createdPlanned
invoicing.invoice.refundedInvoice payment refundedPlanned
invoicing.invoice.sentInvoice sent to customerPlanned
invoicing.invoice.unpaidInvoice became overduePlanned
invoicing.invoice.updatedInvoice details updatedPlanned
invoicing.invoice.reminder-sentPayment reminder sentPlanned
billing.invoice.payment-failedInvoice payment attempt failedPlanned
billing.invoice.createdWatenga platform subscription invoice generatedLive

SUBSCRIPTION

EventDescriptionStatus
billing.subscription.activatedSubscription is activePlanned
billing.subscription.cancelledSubscription cancelledPlanned
billing.subscription.createdNew subscription createdPlanned
billing.subscription.expiredSubscription expiredPlanned
billing.subscription.payment-failedRecurring payment failedPlanned
billing.subscription.re-activatedSubscription re-activatedPlanned
billing.subscription.suspendedSubscription suspendedPlanned
billing.subscription.updatedSubscription plan or quantity updatedPlanned

BILLING_PLAN

EventDescriptionStatus
billing.plan.activatedBilling plan activatedPlanned
billing.plan.createdBilling plan createdPlanned
billing.plan.deactivatedBilling plan deactivatedPlanned
billing.plan.updatedBilling plan updatedPlanned

DISPUTE

EventDescriptionStatus
customer.dispute.createdChargeback or dispute openedLive
customer.dispute.updatedDispute status changedPlanned
customer.dispute.resolvedDispute won, lost, or acceptedPlanned
risk.dispute.createdRisk team opened dispute casePlanned
customer.dispute.evidence-submittedDispute evidence package submittedPlanned

CUSTOMER_PAYOUT

EventDescriptionStatus
customer.payout.completedEnd-customer payout completedPlanned
customer.payout.failedEnd-customer payout failedPlanned

MERCHANT_ONBOARDING

EventDescriptionStatus
merchant.onboarding.completedMerchant finished onboardingPlanned
merchant.onboarding.submittedSub-merchant submitted KYC for reviewLive
merchant.onboarding.kyc-approvedSub-merchant KYC approvedLive
merchant.onboarding.kyc-rejectedSub-merchant KYC rejectedPlanned
merchant.onboarding.document-requestedAdditional KYC document requestedPlanned

MERCHANT_ACCOUNT

EventDescriptionStatus
customer.managed-account.createdConnected account createdPlanned
customer.managed-account.status-changedAccount status updatedPlanned
customer.managed-account.risk-assessedRisk assessment completedPlanned
customer.managed-account.updatedAccount profile updatedPlanned
merchant.account.balance-updatedMerchant balance changedPlanned
merchant.settings.updatedMerchant settings changedPlanned

VAULT

EventDescriptionStatus
vault.payment-token.createdPayment token vaultedPlanned
vault.payment-token.deletedPayment token deletedPlanned
vault.payment-token.updatedPayment token updatedPlanned

COMPLIANCE

EventDescriptionStatus
compliance.process.completedCompliance process completedPlanned
compliance.process.failedCompliance process failedPlanned
compliance.process.end-user-action-requiredMerchant action required for compliancePlanned
compliance.screening.completedSanctions/PEP screening completedPlanned

IDENTITY

EventDescriptionStatus
identity.consent.grantedIdentity consent grantedPlanned
identity.consent.revokedIdentity consent revokedPlanned

REPORTING

EventDescriptionStatus
reporting.report.availableScheduled report ready for downloadPlanned

AGENT

EventDescriptionStatus
agent.cash-out.completedAgent cash-out completedPlanned
agent.cash-out.failedAgent cash-out failedPlanned
agent.cash-out.heldAgent cash-out held for reviewPlanned
agent.float.topped-upAgent float increasedPlanned
agent.float.lowAgent float below thresholdPlanned
agent.float.depletedAgent float depletedPlanned
agent.suspendedAgent account suspendedPlanned
agent.re-activatedAgent account re-activatedPlanned

CAPITAL

EventDescriptionStatus
capital.offer_createdMerchant has a new Watenga Capital advance offerLive
capital.disbursedAdvance funds credited to merchant balanceLive
capital.repaymentHoldback deducted from settlementLive
capital.repaidAdvance fully repaidLive