> ## Documentation Index
> Fetch the complete documentation index at: https://vivawallet-sdk-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook Helper Functions for VivaWallet Events

> Use the SDK's exported webhook helper functions to answer URL verification challenges, verify Data Services signatures, extract idempotency keys, and soft-validate Viva webhook payloads.

The SDK exports webhook helper functions from the package root. They are not methods on `vivawallet.webhooks`; use `vivawallet.webhooks` for subscription management, and import these helpers when building your webhook receiver.

```typescript theme={null}
import {
  buildVivaMerchantWebhookVerificationBody,
  buildVivaMerchantWebhookVerificationResponse,
  buildVivaIsvWebhookVerificationBody,
  buildVivaIsvWebhookVerificationResponse,
  verifyVivaDataServicesWebhookSignature,
  extractVivaWebhookIdentity,
  getVivaWebhookIdempotencyKey,
  validateVivaTransactionPaymentCreatedWebhook,
} from '@nkhind/vivawallet-sdk';
```

These helpers are intentionally tolerant. Viva webhook payloads can gain extra fields over time, and some fields differ between event families. The validators check the fields that matter for routing, reconciliation, idempotency, and business expectations; they do not perform strict schema validation.

<Note>
  Soft validation does not replace a server-side confirmation step when your workflow requires one. For payment state changes, you can still retrieve the order or transaction from Viva before mutating critical internal state.
</Note>

## URL verification helpers

When configuring webhooks, Viva can verify your endpoint by expecting your receiver to return a JSON body containing the webhook verification key. Merchant/Marketplace verification uses `Key`, while ISV verification uses `key`.

Retrieve the key with the client module first:

```typescript theme={null}
const keyResult = await vivawallet.webhooks.retrieveWebhookKey();

if (!keyResult.success || !keyResult.data) {
  throw new Error(keyResult.message);
}
```

### `buildVivaMerchantWebhookVerificationBody(key)`

Builds only the JSON body expected by merchant and marketplace webhook verification.

```typescript theme={null}
const body = buildVivaMerchantWebhookVerificationBody(keyResult.data);
// { Key: '...' }
```

Use this when your framework already controls the HTTP status and headers.

### `buildVivaMerchantWebhookVerificationResponse(key)`

Builds a framework-agnostic response object for merchant and marketplace webhook verification.

```typescript theme={null}
const response = buildVivaMerchantWebhookVerificationResponse(keyResult.data);

res.status(response.statusCode).set(response.headers).json(response.body);
```

The returned object has:

| Field        | Meaning                                   |
| ------------ | ----------------------------------------- |
| `statusCode` | Always `200`                              |
| `headers`    | Includes `Content-Type: application/json` |
| `body`       | `{ Key: key }`                            |

### `buildVivaIsvWebhookVerificationBody(key)`

Builds only the JSON body expected by ISV webhook verification.

```typescript theme={null}
const keyResult = await vivaIsv.webhook.retrieveWebhookKey();

if (!keyResult.success || !keyResult.data) {
  throw new Error(keyResult.message);
}

const body = buildVivaIsvWebhookVerificationBody(keyResult.data.key);
// { key: '...' }
```

Use this with ISV webhook endpoints created through `vivaIsv.webhook.create()`.

### `buildVivaIsvWebhookVerificationResponse(key)`

Builds a framework-agnostic `200` JSON response for ISV webhook verification.

```typescript theme={null}
const response = buildVivaIsvWebhookVerificationResponse(keyResult.data.key);

res.status(response.statusCode).set(response.headers).json(response.body);
```

## Data Services signature verification

### `verifyVivaDataServicesWebhookSignature(options)`

Verifies signed Data Services webhook notifications, especially Sale Transactions file notifications. It uses the raw request body and Viva's HMAC headers:

| Header               | Use                                                                |
| -------------------- | ------------------------------------------------------------------ |
| `Viva-Signature-256` | SHA-256 HMAC digest of the raw request body                        |
| `Viva-Signature`     | SHA-1 HMAC digest of the raw request body                          |
| `Viva-Delivery-Id`   | Delivery identifier returned in the result for logging/idempotency |
| `Viva-Event`         | Event name returned in the result for logging/routing              |

```typescript theme={null}
const signature = verifyVivaDataServicesWebhookSignature({
  rawBody: req.rawBody,
  headers: req.headers,
  secret: process.env.VIVA_WEBHOOK_SECRET!,
});

if (!signature.valid) {
  console.warn('Invalid Viva Data Services signature', signature.issues);
  return res.sendStatus(401);
}
```

Pass `algorithm: 'sha256'` or `algorithm: 'sha1'` to require one algorithm. When omitted, the helper tries `Viva-Signature-256` first and falls back to `Viva-Signature` when only the SHA-1 header is present.

<Warning>
  Signature verification requires the raw request body before JSON parsing. Configure your HTTP framework to expose a `Buffer`, `Uint8Array`, or string containing the exact bytes Viva sent.
</Warning>

The result includes:

| Field        | Meaning                                                           |
| ------------ | ----------------------------------------------------------------- |
| `valid`      | `true` when the computed HMAC matches the Viva header             |
| `algorithm`  | `sha256`, `sha1`, or `null` when no usable signature is available |
| `issues`     | Missing or mismatched field diagnostics                           |
| `signature`  | Header signature used for the comparison, when present            |
| `deliveryId` | `Viva-Delivery-Id`, when present                                  |
| `event`      | `Viva-Event`, when present                                        |

## Identity and idempotency helpers

### `extractVivaWebhookIdentity(payload, headers?)`

Extracts envelope identity fields and Data Services headers into one small object for logging, tracing, and routing.

```typescript theme={null}
const identity = extractVivaWebhookIdentity(req.body, req.headers);

console.log(identity.eventTypeId);
console.log(identity.messageId);
console.log(identity.deliveryId);
console.log(identity.retryCount);
```

It reads envelope fields such as `EventTypeId`, `MessageId`, `CorrelationId`, `Created`, `RetryCount`, `RetryDelayInSeconds`, `RecipientId`, and `MessageTypeId`. It also reads `Viva-Delivery-Id` and `Viva-Event` from headers when present.

Use this helper to add structured logs before branching on an event. It does not validate that the event is trusted.

### `getVivaWebhookIdempotencyKey(payload, headers?)`

Builds a stable key you can use to deduplicate retries. The helper checks fields in priority order:

1. `MessageId` from the Viva envelope.
2. `Viva-Delivery-Id` from headers.
3. Stable event data identifiers such as `TransactionId`, `OrderCode`, `TransferId`, `ObligationId`, `CaptureId`, `ConnectedAccountId`, `PersonId`, `SessionId`, `CommandId`, or `BankTransferId`.

```typescript theme={null}
const idempotency = getVivaWebhookIdempotencyKey(req.body, req.headers);

if (!idempotency.success) {
  console.warn('No stable idempotency key', idempotency.warnings);
} else if (await alreadyProcessed(idempotency.key)) {
  return res.sendStatus(200);
}
```

The result tells you which field was used and whether the key came from the envelope, headers, or event data.

## Soft validation pattern

Every validation helper returns `VivaWebhookSoftValidationResult<T>`:

```typescript theme={null}
const validation = validateVivaTransactionPaymentCreatedWebhook(req.body, {
  expectedOrderCode: '1234567890123456',
  expectedAmount: 1000,
});

if (!validation.valid) {
  console.warn(validation.issues);
}

if (validation.data) {
  console.log(validation.data.TransactionId);
}
```

The payload can be either the full Viva envelope (`{ EventData: ... }`) or the event data object itself. The optional `expectations` object lets you verify business values such as order code, amount, status, connected account, wallet, session, or command identifiers.

<Info>
  Decide your HTTP response policy deliberately. Signature failures usually warrant a non-`200` response. Soft validation mismatches may be better acknowledged with `200` and quarantined internally if you do not want Viva to retry the same delivery.
</Info>

## Payment result validators

### `validateVivaPaymentResultWebhook(payload, expectations?)`

Generic validator for payment-result style events. It requires `OrderCode`, `TransactionId`, `StatusId`, and `Amount`, and can compare `CurrencyCode` when `expectedCurrencyCode` is provided.

Use it when you intentionally handle several payment-result events through one branch.

### `validateVivaTransactionPaymentCreatedWebhook(payload, expectations?)`

Semantic wrapper for `Transaction Payment Created`. Use it for successful Smart Checkout payment notifications.

```typescript theme={null}
const validation = validateVivaTransactionPaymentCreatedWebhook(req.body, {
  expectedOrderCode: order.orderCode,
  expectedAmount: order.amount,
});
```

### `validateVivaTransactionFailedWebhook(payload, expectations?)`

Validates the same core payment fields and additionally requires `ResponseEventId`, which is useful for failed-payment diagnostics.

```typescript theme={null}
const validation = validateVivaTransactionFailedWebhook(req.body, {
  expectedOrderCode: order.orderCode,
});
```

### `validateVivaTransactionReversalCreatedWebhook(payload, expectations?)`

Semantic wrapper for `Transaction Reversal Created`. Use it for reversal/refund-style transaction notifications where you expect the same core payment-result fields.

## Order validator

### `validateVivaOrderUpdatedWebhook(payload, expectations?)`

Validates `Order Updated` events. It requires `OrderCode` and `IsCancelled`, and can compare both with `expectedOrderCode` and `expectedIsCancelled`.

```typescript theme={null}
const validation = validateVivaOrderUpdatedWebhook(req.body, {
  expectedOrderCode: orderCode,
  expectedIsCancelled: true,
});
```

Use this event to detect order cancellation updates. Check `validation.data?.IsCancelled` before treating the order as cancelled.

## Connected account validator

### `validateVivaAccountConnectionWebhook(payload, expectations?)`

Validates account lifecycle events for Marketplace and ISV integrations, including `Account Connected` and `Account Verification Status Changed` payload shapes.

It requires `ConnectedAccountId` and can compare optional values such as `PersonId`, `PlatformPersonId`, `WalletId`, and `Verified`.

```typescript theme={null}
const validation = validateVivaAccountConnectionWebhook(req.body, {
  expectedConnectedAccountId: seller.accountId,
});
```

Use it when syncing seller or connected merchant onboarding status.

## Marketplace and obligation validators

### `validateVivaTransferCreatedWebhook(payload, expectations?)`

Validates `Transfer Created` events. It requires `TransferId` and can compare transaction, amount, currency, target wallet, target person, and transfer type values.

```typescript theme={null}
const validation = validateVivaTransferCreatedWebhook(req.body, {
  expectedTransferId: transferId,
  expectedAmount: 2500,
});
```

### `validateVivaObligationWebhook(payload, expectations?)`

Validates deprecated obligation webhook payloads. It requires `ObligationId` and can compare `CaptureId`, amount, currency, target wallet, and target person values.

Use it only for integrations that still rely on Viva's deprecated obligation flow.

## POS and operational validators

### `validateVivaPosSessionWebhook(payload, expectations?)`

Validates POS ECR session webhooks such as session-created and session-failed notifications. It checks `SessionId`, `TransactionId`, `StatusId`, `Amount`, and `CurrencyCode`, and requires at least one stable session identifier: `SessionId` or `TransactionId`.

```typescript theme={null}
const validation = validateVivaPosSessionWebhook(req.body, {
  expectedSessionId: sessionId,
});
```

### `validateVivaTransactionPriceCalculatedWebhook(payload, expectations?)`

Validates `Transaction Price Calculated` payloads. It can compare `TransactionId`, `OrderCode`, `Amount`, and `CurrencyCode` when you pass expectations.

This helper is intentionally lightweight because the event can be used for pricing and fee-related flows where only a subset of fields may matter to your application.

### `validateVivaAccountTransactionWebhook(payload, expectations?)`

Validates `Account Transaction Created` events. It requires `TransactionId` and can compare amount, currency, wallet, and account IDs.

```typescript theme={null}
const validation = validateVivaAccountTransactionWebhook(req.body, {
  expectedWalletId: walletId,
});
```

### `validateVivaBankTransferCommandWebhook(payload, expectations?)`

Validates `Command Bank Transfer Created` and `Command Bank Transfer Executed` events. It checks command, bank transfer, transaction, amount, currency, and wallet fields. At least one of `CommandId`, `BankTransferId`, or `TransactionId` must be present.

```typescript theme={null}
const validation = validateVivaBankTransferCommandWebhook(req.body, {
  expectedCommandId: commandId,
});
```

## Complete receiver sketch

```typescript theme={null}
import {
  extractVivaWebhookIdentity,
  getVivaWebhookIdempotencyKey,
  validateVivaTransactionPaymentCreatedWebhook,
} from '@nkhind/vivawallet-sdk';

app.post('/webhooks/viva', async (req, res) => {
  const identity = extractVivaWebhookIdentity(req.body, req.headers);
  const idempotency = getVivaWebhookIdempotencyKey(req.body, req.headers);

  if (idempotency.success && await alreadyProcessed(idempotency.key)) {
    return res.sendStatus(200);
  }

  if (identity.eventTypeId === 1796) {
    const validation = validateVivaTransactionPaymentCreatedWebhook(req.body);

    if (!validation.valid) {
      await quarantineWebhook({ identity, issues: validation.issues, body: req.body });
      return res.sendStatus(200);
    }

    await markOrderPaid(validation.data!.OrderCode, validation.data!.TransactionId);
  }

  return res.sendStatus(200);
});
```

## Official webhook references

* [Webhook event index](https://developer.viva.com/webhooks-for-payments/)
* [Setting up webhooks](https://developer.viva.com/webhooks-for-payments/setting-up-webhooks/)
* [Sale Transactions signed webhook](https://developer.viva.com/webhooks-for-payments/sale-transactions/)
* [Transaction Payment Created](https://developer.viva.com/webhooks-for-payments/transaction-payment-created/)
* [Order Updated](https://developer.viva.com/webhooks-for-payments/order-updated/)
