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

# Set up IPN webhooks for payment notifications

> Register an IPN URL with PesaPal through FinConnect, set up an Express handler to receive payment notifications, and connect the ipnId to your pay() calls.

Instant Payment Notifications (IPNs) allow FinConnect-powered apps to receive real-time payment status updates from the provider. When a customer completes or fails a payment, the provider sends an HTTP request to your registered IPN URL so your server can update the order status immediately.

<Note>
  Currently, only PesaPal supports IPN registration via FinConnect. Calling `registerIpn()` on a ClickPesa SDK instance will throw an error: `"IPN registration not supported by this provider"`.
</Note>

## Register an IPN URL

Call `sdk.registerIpn()` with your publicly accessible callback URL and the notification type (`GET` or `POST`). The method authenticates with PesaPal, registers the URL, and returns an `ipnId`.

```typescript theme={null}
// Register with GET notification type
const ipnId = await sdk.registerIpn('https://yourapp.com/ipn', 'GET');

// Or POST notification type
const ipnId = await sdk.registerIpn('https://yourapp.com/ipn', 'POST');
```

The returned `ipnId` is stored by PesaPal and used to identify your notification endpoint. You must pass it to every subsequent `pay()` call.

## Set up your IPN handler

Your IPN handler must respond with HTTP `200` quickly. PesaPal sends `OrderTrackingId`, `OrderMerchantReference`, and `OrderNotificationType` as query parameters for GET-type notifications.

```typescript theme={null}
import express from 'express';
const app = express();
app.use(express.json());

// GET-based IPN handler
app.get('/ipn', (req, res) => {
  const { OrderTrackingId, OrderMerchantReference, OrderNotificationType } = req.query;

  // Verify payment status using OrderTrackingId
  console.log('IPN received:', { OrderTrackingId, OrderMerchantReference });

  res.status(200).send('OK');
});
```

<Tip>
  Use a dedicated `/ipn` route and return `200` as quickly as possible. Perform any database updates or downstream API calls asynchronously so the response is not delayed.
</Tip>

<Warning>
  Your IPN URL must be publicly accessible — PesaPal cannot reach `localhost`. Use a tunneling service such as [ngrok](https://ngrok.com) during local development to expose your server to the internet.
</Warning>

## Connect IPN registration to payments

The full flow is: register the IPN URL, capture the returned `ipnId`, then pass it to `pay()`. FinConnect merges the `ipnId` into the request payload as `notification_id` automatically.

```typescript theme={null}
import { FintechSDK } from 'finconnect';

const sdk = new FintechSDK({
  provider: 'pesapal',
  config: {
    baseUrl: process.env.PESAPAL_BASE_URL!,
    PESAPAL_CONSUMER_KEY: process.env.PESAPAL_CONSUMER_KEY!,
    PESAPAL_CONSUMER_SECRET: process.env.PESAPAL_CONSUMER_SECRET!,
  }
});

// Step 1: Register your IPN URL
const ipnId = await sdk.registerIpn('https://yourapp.com/ipn', 'GET');

// Step 2: Pass ipnId to pay()
const result = await sdk.pay({
  id: 'ORD-2024-001',
  currency: 'KES',
  amount: 1500,
  description: 'Order #2024-001',
  callback_url: 'https://yourapp.com/callback',
  billing_address: {
    email_address: 'customer@example.com',
    phone_number: '254712345678',
    first_name: 'Jane',
    last_name: 'Doe',
  }
}, ipnId);
```

## Azampay callback

## Setting callback api

Azampay uses an HTTP `POST` Request to send back the transaction status. Finconnect helps to fetch the public key for verifying the signature to ensure the data sent isn't tampered with.

Call ` sdk.handlecallback()` to fetch the public key and verify the signature automatically.

```typescript theme={null}
import { FintechSDK } from 'finconnect';

const sdk = new FintechSDK({
  provider: 'azampay',
  config: {
    baseUrl: process.env.AZAMPAY_BASE_URL || 'https://sandbox.azampay.co.tz',
    AZAMPAY_APP_NAME: process.env.AZAMPAY_APP_NAME,
    AZAMPAY_CONSUMER_KEY: process.env.AZAMPAY_CLIENT_ID,
    AZAMPAY_CONSUMER_SECRET: process.env.AZAMPAY_API_KEY
  }
});

app.post('/azampay/callback', async (req, res) => {
  console.log('Received AzamPay Callback:', req.body);
  try {
    // This will fetch the public key and verify the signature automatically
    const result = await sdk.handleCallback(req.body);
    
    if (result.isValid) {
      const transactionData = result.data;
      console.log('✅ Payment Signature Verified!', transactionData);
      
      // TODO: Update your database using transactionData
      // Example: await updateOrder(transactionData.utilityref, transactionData.transactionstatus);
      
      res.status(200).send('OK');
    } else {
      console.error('❌ Invalid signature received in callback');
      res.status(400).send('Invalid Signature');
    }
  } catch (error: any) {
    console.error('Callback processing error:', error.message);
    res.status(500).send('Internal Server Error');
  }
});
```
