> For the complete documentation index, see [llms.txt](https://tembo.gitbook.io/tembo/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tembo.gitbook.io/tembo/merchant-accounts-api/api-reference/webhook-notification.md).

# Webhook Notification

### 💡 What to Expect

Webhook notifications are sent shortly after transactions are **reflected** in your collection accounts.\
Reflection times depend on how the originating and receiving banks process and settle payments.

The actual reflection time may vary depending on several factors:

* **Payment channel and amount** – Smaller payments are often settled instantly through a faster payment channel, whereas larger or interbank transfers may take longer to clear.
* **Bank processing schedules** – Some banks apply incoming credits immediately after settlement, others during clearing windows.
* **Non-working days (RTGS only)** – Real-time gross settlement (TISS) payments are not processed on weekends or public holidays and will be applied on the next business day.

TemboPlus dispatches webhook notifications once your account balance is updated, ensuring your system receives reliable transaction events as soon as they are available.

{% hint style="info" %}
For detailed information on payment channels, cutoff times, and clearing schedules in Tanzania, you can request the official **Bank Payment Processing Reference** from TemboPlus Support.
{% endhint %}

### 📨 Webhook Request Format <a href="#webhook-request-format" id="webhook-request-format"></a>

Your webhook endpoint will receive HTTP POST requests with these characteristics:

* **Method**: POST
* **Content-Type**: application/json
* **Headers**: Standard HTTP headers plus `x-request-id` for tracing

#### **Request Body Structure**

```
{
  "timestamp": "2025-09-15T12:34:56+03:00",
  "signature": "base64-encoded-hmac-sha256",
  "payload": "stringified-json-of-transaction-data"
}
```

#### **Transaction Data (inside payload string)**

```json
{
  "event": "transaction.created",
  "account": {
    "accountNo": "1234567890",
    "accountName": "Business Account Ltd",
    "bankId": "local bank identifier e.g. CRDB"
  },
  "transaction": {
    "id": "unique-transaction-id",
    "transactionId": "bank-transaction-id-or-null",
    "reference": "TXN-REF-123456",
    "paymentReference": "PAY-REF-789",
    "transactionDate": "2025-09-15T10:30:00+03:00",
    "creditOrDebit": "CREDIT",
    "currency": "TZS",
    "amountCredit": 50000.00,
    "amountDebit": 0,
    "availableBalance": 150000.00,
    "currentBalance": 150000.00,
    "description": "Customer payment received"
  },
  "counterparty": {
    "accountNo": "1234567890",
    "accountName": "Customer Account Ltd",
    "bankId": "local bank identifier e.g. CRDB"
  }, // counterparty may be null
}
```

### 🔐 Security Verification <a href="#security-verification" id="security-verification"></a>

#### **HMAC Signature Validation Process**

Each webhook includes a cryptographic signature that you must verify:

1. **Extract Components**: Get `timestamp`, `signature`, and `payload` from request body
2. **Create Signing Content**: Concatenate `timestamp + payload` as a string
3. **Calculate HMAC**: Use HMAC-SHA256 with your provided hash key (base64 decode the key first)
4. **Encode Result**: Base64 encode the HMAC result
5. **Compare**: The calculated signature must exactly match the received signature

#### **Signature Validation Algorithm**

```
signing_content = timestamp + payload
calculated_hmac = HMAC-SHA256(base64_decode(your_hash_key), signing_content)
expected_signature = base64_encode(calculated_hmac)
is_valid = (expected_signature === received_signature)
```

#### **Example Implementation (Node.js)**

```javascript
const crypto = require('crypto');

function validateWebhook(envelope, yourHashKey) {
  const { timestamp, signature, payload } = envelope;
  
  // Create signing content by concatenating timestamp and payload
  const signingContent = timestamp + payload;
  
  // Calculate HMAC-SHA256 signature
  const expectedSignature = crypto
    .createHmac('sha256', Buffer.from(yourHashKey, 'base64'))
    .update(signingContent, 'utf-8')
    .digest('base64');
  
  // Compare signatures
  return signature === expectedSignature;
}
```

### 🏗️ Implementation Requirements <a href="#implementation-requirements" id="implementation-requirements"></a>

#### **1. Endpoint Configuration**

* **Protocol**: HTTPS only (HTTP not supported)
* **Method**: Accept POST requests
* **Content-Type**: Parse `application/json`
* **Response Time**: Respond within 30 seconds
* **Response Code**: Return HTTP 200 for successful processing

#### **2. Request Processing Flow**

1. **Receive Request**: Accept POST with JSON body
2. **Validate Signature**: Verify HMAC signature (reject if invalid)
3. **Parse Payload**: Extract transaction data from `payload` string
4. **Check Duplicates**: Handle potential duplicate deliveries
5. **Process Transaction**: Execute your business logic
6. **Respond**: Return HTTP 200 success response

#### **3. Idempotency Handling**

Webhooks may be delivered multiple times. Implement duplicate detection using:

* **Transaction ID**: Use the unique `transaction.id` field
* **Storage**: Track processed transaction IDs in database/cache
* **Response**: Return HTTP 200 for already-processed transactions

### 🔄 Delivery Behavior <a href="#delivery-behavior" id="delivery-behavior"></a>

#### **Retry Policy**

Our system automatically retries failed webhook deliveries:

* **Success Criteria**: HTTP status codes 200-299
* **Permanent Failures**: HTTP 400-499 (except 408 Request Timeout, 429 Too Many Requests)
* **Temporary Failures**: HTTP 500-599, network errors, timeouts
* **Retry Schedule**: Exponential backoff starting at 2 seconds
* **Maximum Attempts**: Up to 20 retries over 24 hours

#### **Expected Response Format**

Your endpoint should return a simple HTTP 200 success response:

```json
{
  "message": "Transaction processed successfully",
  "transactionId": "unique-transaction-id"
}
```

### 💻 Implementation Example <a href="#implementation-example" id="implementation-example"></a>

#### **Conceptual Flow (Language Agnostic)**

```
1. Receive HTTP POST request
2. Parse JSON request body
3. Validate HMAC signature
   - If invalid: Return HTTP 401
4. Extract transaction data from payload
5. Check if transaction already processed
   - If duplicate: Return HTTP 200
6. Execute business logic:
   - Update account balances
   - Trigger notifications
   - Log transaction
   - Update records
7. Mark transaction as processed
8. Return HTTP 200 success
```

#### **Node.js Implementation Example**

```javascript
const express = require('express');
const app = express();
app.use(express.json());

// In-memory storage (use database in production)
const processedTransactions = new Set();

app.post('/webhook/transactions', async (req, res) => {
  try {
    // 1. Validate webhook signature
    if (!validateWebhook(req.body, YOUR_HASH_KEY)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }
    
    // 2. Parse transaction data
    const data = JSON.parse(req.body.payload);
    const transactionId = data.transaction.id;
    
    // 3. Handle duplicates
    if (processedTransactions.has(transactionId)) {
      return res.status(200).json({ message: 'Already processed' });
    }
    
    // 4. Process transaction
    await processTransaction(data);
    
    // 5. Mark as processed
    processedTransactions.add(transactionId);
    
    // 6. Return success
    res.status(200).json({ 
      message: 'Transaction processed',
      transactionId 
    });
    
  } catch (error) {
    console.error('Webhook processing error:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

async function processTransaction(webhookData) {
  const { account, transaction } = webhookData;
  
  // Your business logic here
  console.log(`Processing ${transaction.creditOrDebit} of ${transaction.currency} ${transaction.amountCredit || transaction.amountDebit} on account ${account.accountNo}`);
  
  // Example actions:
  // - Update local account balance
  // - Send customer notification
  // - Trigger reconciliation process
  // - Log to audit trail
}
```

### ⚙️ Setup Requirements <a href="#setup-requirements" id="setup-requirements"></a>

#### **Partner Configuration**

1. **Webhook URL**: Provide your HTTPS endpoint URL
2. **Hash Key**: You'll receive a base64-encoded signing key
3. **Account Allocation**: Ensure accounts are allocated to your partner account

#### **Testing Your Implementation**

**Test Request Format**

```
POST https://your-domain.com/webhook/transactions
Content-Type: application/json

{
  "timestamp": "2025-09-15T12:00:00+03:00",
  "signature": "calculated-signature-here",
  "payload": "{\"event\":\"transaction.created\",\"account\":{\"accountNo\":\"TEST123\",\"accountName\":\"Test Account\",\"bankId\":\"test-bank\"},\"transaction\":{\"id\":\"TEST-001\",\"reference\":\"TEST-REF\",\"transactionDate\":\"2025-09-15T11:30:00+03:00\",\"creditOrDebit\":\"CREDIT\",\"currency\":\"TZS\",\"amountCredit\":1000,\"amountDebit\":0,\"currentBalance\":5000,\"description\":\"Test payment\"}}"
}
```

### ✅ Implementation Checklist <a href="#implementation-checklist" id="implementation-checklist"></a>

**Essential Requirements**

* [ ] HTTPS webhook endpoint configured
* [ ] HMAC-SHA256 signature validation implemented
* [ ] JSON request parsing
* [ ] Transaction ID deduplication logic
* [ ] HTTP 200 response for successful processing
* [ ] Error handling for malformed requests

**Recommended Enhancements**

* [ ] Persistent storage for processed transaction IDs
* [ ] Logging and monitoring of webhook deliveries
* [ ] Retry mechanism for downstream processing failures
* [ ] Alert system for webhook processing errors

### 🐛 Troubleshooting <a href="#troubleshooting" id="troubleshooting"></a>

#### **Common Issues**

**No webhooks received**

* Verify endpoint URL is publicly accessible via HTTPS
* Confirm accounts are properly allocated to your partner
* Check that endpoint responds with HTTP 200

**Signature validation failures**

* Ensure hash key is stored and used correctly
* Verify signing content is exactly `timestamp + payload` (string concatenation)
* Confirm payload is the raw JSON string, not a parsed object
* Check that hash key is base64-decoded before HMAC calculation

**Duplicate processing**

* Implement idempotency using the unique `transaction.id`
* Store processed IDs in persistent storage (database, not memory)
* Always return HTTP 200 for already-processed transactions

**Processing timeouts**

* Ensure webhook processing completes within 30 seconds
* Consider async processing for complex business logic
* Return HTTP 200 immediately, then process in background

### 📞 Support <a href="#support" id="support"></a>

For webhook integration assistance, contact our technical support team with:

* Your partner identifier
* Webhook endpoint URL
* Sample request/response logs
* Error messages or unexpected behavior details

***

**🔒 Security Note**: Always validate webhook signatures before processing transaction data to ensure authenticity and prevent unauthorized access to your system.
