Skip to main content
The Bank Intent Response webhook is sent to merchants when customers make decisions about billing intents. This webhook contains the customer’s decision and any spending limits they’ve set.

When This Webhook is Sent

Trigger Event

Sent to merchants when a customer responds to a billing intent through their bank, either approving, denying, or setting limits.
Webhook Type: billing.intent.response Recipient: Merchants and Providers Frequency: Real-time (immediate)

Webhook Payload Structure

{
  "entityId": "merchant_001",
  "entityType": "merchant",
  "webhookType": "billing.intent.response",
  "timestamp": "2024-01-15T10:35:00.000Z",
  "batchId": "batch_response_456",
  "data": [
    {
      "customerId": "customer_123",
      "agreementPhrase": "customer-agreement-phrase-abc123",
      "entityId": "merchant_001",
      "lookupPhrase": "customer-lookup-phrase-xyz789",
      "customerDecision": "approve_recurring",
      "spendingLimits": {
        "single": 5000,
        "daily": 10000,
        "weekly": 50000,
        "monthly": 200000
      },
      "bankId": "bank_456",
      "blockMerchant": false,
      "reference": "intent_response_ref_789"
    }
  ],
  "itemCount": 1,
  "signature": "webhook_signature_hash"
}

Customer Decision Types

approve_once

Customer approves billing but requires approval for each transaction

approve_recurring

Customer pre-approves future billing within spending limits

deny

Customer rejects the billing agreement

suspended

Customer temporarily suspends billing capability

Implementation Guide

app.post("/webhooks/peere/intent-response", (req, res) => {
  try {
    // Verify webhook signature
    const signature = req.headers["x-peere-signature"];
    if (!verifySignature(req.body, signature)) {
      return res.status(401).send("Invalid signature");
    }

    const { webhookType, data } = req.body;

    if (webhookType === "billing.intent.response") {
      handleIntentResponse(data);
    }

    res.status(200).send("OK");
  } catch (error) {
    console.error("Webhook processing error:", error);
    res.status(500).send("Internal Server Error");
  }
});

function handleIntentResponse(responseData) {
  responseData.forEach((response) => {
    const { customerDecision, agreementPhrase, customerId, spendingLimits } = response;

    switch (customerDecision) {
      case "approve_once":
        // Store agreement phrase for one-time use
        storeCustomerPhrase(customerId, agreementPhrase, "once", spendingLimits);
        notifyMerchant("Customer approved one-time billing", response);
        break;

      case "approve_recurring":
        // Store agreement phrase for recurring use
        storeCustomerPhrase(customerId, agreementPhrase, "recurring", spendingLimits);
        notifyMerchant("Customer approved recurring billing", response);
        // Can now bill customer automatically within limits
        break;

      case "deny":
        // Customer rejected billing
        logRejection(customerId, response.reference);
        notifyMerchant("Customer denied billing request", response);
        break;

      case "suspended":
        // Customer suspended billing
        suspendCustomerBilling(customerId);
        notifyMerchant("Customer suspended billing", response);
        break;
    }
  });
}

function storeCustomerPhrase(customerId, agreementPhrase, type, limits) {
  // Store in your database for future billing
  const customerBilling = {
    customerId: customerId,
    agreementPhrase: agreementPhrase,
    billingType: type,
    spendingLimits: limits,
    createdAt: new Date(),
    isActive: true,
  };

  // Save to database
  saveBillingAgreement(customerBilling);
}

Using Approved Agreement Phrases

Once you receive an approved agreement phrase, you can use it to bill the customer:
async function billCustomer(customerId, amount, description) {
  try {
    // Get customer's billing agreement
    const agreement = await getBillingAgreement(customerId);

    if (!agreement || !agreement.isActive) {
      throw new Error("No active billing agreement found");
    }

    // Check spending limits
    if (!checkSpendingLimits(agreement, amount)) {
      throw new Error("Amount exceeds spending limits");
    }

    // Bill the customer
    const response = await axios.post(
      "https://api.peere.network/v1/customer/bill",
      {
        agreementPhrases: [agreement.agreementPhrase],
        amount: amount,
        currency: "NGN",
        description: description,
      },
      {
        headers: {
          "Authorization": `Bearer ${process.env.PEERE_API_KEY}`,
          "Content-Type": "application/json",
        },
      },
    );

    return response.data;
  } catch (error) {
    console.error("Billing failed:", error);
    throw error;
  }
}

function checkSpendingLimits(agreement, amount) {
  const limits = agreement.spendingLimits;

  // Check single transaction limit
  if (limits.single && amount > limits.single) {
    return false;
  }

  // Check daily limit (implement your own tracking)
  const dailySpent = getDailySpentAmount(agreement.customerId);
  if (limits.daily && dailySpent + amount > limits.daily) {
    return false;
  }

  // Check monthly limit
  const monthlySpent = getMonthlySpentAmount(agreement.customerId);
  if (limits.monthly && monthlySpent + amount > limits.monthly) {
    return false;
  }

  return true;
}

Handling Different Decision Types

// Customer approved recurring billing
if (customerDecision === 'approve_recurring') {
  // Store for future automatic billing
  await storeRecurringAgreement({
    customerId,
    agreementPhrase,
    spendingLimits,
    canBillAutomatically: true
  });
  
  // Set up subscription if applicable
  if (isSubscriptionService) {
    await createSubscriptionSchedule(customerId, agreementPhrase);
  }
  
  // Notify customer of successful setup
  await sendConfirmationEmail(customerId, 'recurring_approved');
}

Testing Your Implementation

curl -X POST "https://your-domain.com/webhooks/peere/intent-response" \
  -H "Content-Type: application/json" \
  -H "X-Peere-Signature: sha256=test_signature" \
  -d '{
    "entityId": "merchant_test_001",
    "entityType": "merchant",
    "webhookType": "billing.intent.response",
    "timestamp": "2024-01-15T10:35:00.000Z",
    "batchId": "test_batch_456",
    "data": [
      {
        "customerId": "test_customer_123",
        "agreementPhrase": "test-agreement-phrase-abc123",
        "entityId": "merchant_test_001",
        "lookupPhrase": "test-lookup-phrase-xyz789",
        "customerDecision": "approve_recurring",
        "spendingLimits": {
          "single": 5000,
          "daily": 10000,
          "monthly": 50000
        },
        "bankId": "test_bank_456",
        "reference": "test_ref_789"
      }
    ],
    "itemCount": 1
  }'

Best Practices

Data Management

  • Store agreement phrases securely - Implement proper data retention policies - Track spending limits accurately - Maintain audit logs

Customer Experience

  • Send confirmation notifications - Respect spending limits - Provide clear billing descriptions - Handle rejections gracefully

Security

  • Encrypt stored agreement phrases - Validate webhook signatures - Implement rate limiting - Monitor for suspicious activity

Reliability

  • Handle webhook retries - Implement idempotency - Monitor processing success - Set up error alerting

Next Steps

Billing Customers

Learn how to bill customers with approved phrases

Billing Request Webhook

Handle billing request notifications

Spending Limits

Implement and manage customer spending limits

API Reference

Explore merchant API endpoints