Getting started

Generating a payment URL

To direct your customer to the Bob Pay payment screen, you will need to create a payment URL and then navigate to that URL.

  • Sandbox environment: https://sandbox.bobpay.co.za
  • Production environment: https://my.bobpay.co.za

Create the payment URL

Endpoint: POST /payments/intents/link

Authentication: Required - Bearer token (a JWT obtained via /login, or your API key sent as the bearer token)

Description: Creates a payment intent and returns a unique payment URL. Your application should navigate to this URL to direct your customer to the Bob Pay payment page, where they can complete their payment using their preferred payment method.

Request headers

Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN

Request body

Parameter Type Required Description
amount float Yes The payment amount in ZAR. Must be greater than zero.
email string Conditional* Customer's email address.
mobile_number string Conditional* Customer's mobile number in international format (e.g., +27821234567).
item_name string No Name of the product or service being purchased.
item_description string No Detailed description of the product or service.
custom_payment_id string No Your unique reference/identifier for this payment. If not provided, a UUID will be generated automatically.
notify_url string No* Webhook URL where payment status notifications will be sent.
success_url string No* URL to redirect the customer after a successful payment.
pending_url string No* URL to redirect the customer when the payment is pending.
cancel_url string No* URL to redirect the customer if they cancel the payment.
short_url boolean No If set to true, a shortened URL will also be generated and returned. Default: false.

* Note: Either email or mobile_number must be provided. The callback URLs are optional, but strongly recommended: without a notify_url you will not receive payment notifications, and without the redirect URLs your customer will not be returned to your site after payment.

Example request

{
  "amount": 1299.99,
  "email": "[email protected]",
  "mobile_number": "+27821234567",
  "item_name": "Wireless Bluetooth Headphones",
  "item_description": "Premium noise-cancelling wireless headphones - Black",
  "custom_payment_id": "ORDER-2024-001",
  "notify_url": "https://yourwebsite.co.za/webhook/payment-notification",
  "success_url": "https://yourwebsite.co.za/payment/success",
  "pending_url": "https://yourwebsite.co.za/payment/pending",
  "cancel_url": "https://yourwebsite.co.za/payment/cancelled",
  "short_url": true
}

Response

Success response (200 OK):

{
  "url": "https://sandbox.bobpay.co.za/pay/ref/3WFFG",
  "short_url": "https://api.sandbox.bob.co.za/r/V4X7WG"
}
Field Type Description
url string The full payment URL to redirect your customer to.
short_url string The shortened payment URL. Equal to url unless short_url: true was requested.

Usage notes

  1. Navigation flow: After receiving the payment URL in the response, your application should immediately redirect/navigate the user's browser to this URL. This typically happens during your checkout process.

  2. Webhook notifications: Bob Pay will send POST requests to your notify_url with payment status updates. Ensure your webhook endpoint is publicly accessible and can handle POST requests.

  3. URL best practices:

    • All callback URLs (notify_url, success_url, pending_url, cancel_url) must be valid URLs; we strongly recommend HTTPS
    • Use the short_url option if you need to store or display a shorter URL
    • Include query parameters in your callback URLs to track which payment they relate to
  4. Custom payment ID: Use the custom_payment_id field to link the Bob Pay payment to your internal order/transaction system. This ID will be included in webhook notifications.

Complete integration example

// Example using Node.js and Express
const axios = require("axios");
const express = require("express");
const app = express();
app.post("/checkout", async (req, res) => {
  try {
    const orderDetails = req.body;
    // Create the payment link
    const response = await axios.post(
      "https://api.sandbox.bobpay.co.za/v2/payments/intents/link",
      {
        amount: orderDetails.totalAmount,
        email: orderDetails.customerEmail,
        mobile_number: orderDetails.customerMobile,
        item_name: orderDetails.productName,
        item_description: orderDetails.productDescription,
        custom_payment_id: orderDetails.orderId,
        notify_url: "https://yourwebsite.com/webhook/payment",
        success_url: `https://yourwebsite.com/order/${orderDetails.orderId}/success`,
        pending_url: `https://yourwebsite.com/order/${orderDetails.orderId}/pending`,
        cancel_url: `https://yourwebsite.com/order/${orderDetails.orderId}/cancelled`,
        short_url: false,
      },
      {
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${process.env.BOBPAY_API_TOKEN}`,
        },
      }
    );
    // Redirect the customer to Bob Pay payment page
    const paymentUrl = response.data.url;
    res.redirect(paymentUrl);
  } catch (error) {
    console.error(
      "Error creating payment link:",
      error.response?.data || error.message
    );
    res.status(500).send("Payment initialisation failed");
  }
});

Payment methods

By default, when a customer visits the payment URL, they are presented with all available payment methods enabled for your merchant account. However, you can direct customers to a specific payment method by specifying the payment_method in the request body.

Include the payment_method parameter when creating the payment link:

{
  "amount": 1299.99,
  "email": "[email protected]",
  "item_name": "Wireless Bluetooth Headphones",
  "notify_url": "https://yourwebsite.co.za/webhook/payment-notification",
  "success_url": "https://yourwebsite.co.za/payment/success",
  "pending_url": "https://yourwebsite.co.za/payment/pending",
  "cancel_url": "https://yourwebsite.co.za/payment/cancelled",
  "payment_method": "credit-card"
}

Available payment methods

Value Description
account-balance Pay using Bob Pay account balance
credit-card Credit or debit card payment (Visa, Mastercard, Amex, Diners)
apple-pay Apple Pay
google-pay Google Pay
instant-eft Real-time bank transfer via Instant EFT
manual-eft Upload proof of payment for manual EFT transfer
pay-shap PayShap instant payment method
capitec-pay Capitec Pay instant payment method
scan-to-pay Scan to Pay (QR code payment)
nedbank-direct-eft Nedbank Direct EFT instant payment method
absa-pay Absa Pay instant payment method

Important notes

  1. Payment method availability: Not all payment methods may be available for your account. Payment methods are subject to:

    • Global payment method activation status
    • Merchant account-specific settings
    • Transaction amount limits per payment method
    • Account status and permissions
  2. Transaction limits: Each payment method has a maximum transaction amount. If the payment amount exceeds the limit for a specified payment method, that method will be shown as inactive.

  3. Fallback behaviour: If you specify a payment method that is not available or inactive, the customer will see it as disabled with an appropriate message.

  4. Multiple payment methods: To allow customers to choose from all available payment methods, simply omit the payment_method parameter from the URL.

Handling notifications

When a payment is completed, Bob Pay sends a POST notification to the notify_url you specified when creating the payment intent. This webhook notification contains the complete payment details.

Webhook request

Method: POST

Content-Type: application/json

Request body example:

{
  "id": 12345,
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "short_reference": "3J9QZ",
  "custom_payment_id": "ORDER-2024-001",
  "amount": 1299.99,
  "paid_amount": 1299.99,
  "total_paid_amount": 1299.99,
  "status": "paid",
  "payment_method": "instant-eft",
  "original_requested_payment_method": "instant-eft",
  "payment_id": 67890,
  "payment": {
    "id": 67890,
    "payment_method_id": 3,
    "payment_method": "instant-eft",
    "amount": 1299.99,
    "status": "success"
  },
  "item_name": "Wireless Bluetooth Headphones",
  "item_description": "Premium noise-cancelling wireless headphones - Black",
  "recipient_account_code": "",
  "recipient_account_id": 100,
  "recipient_account": {
    "id": 100,
    "name": "Your Store",
    "account_code": "ABC123",
    "signup_name": "Your Store",
    "is_merchant": true
  },
  "email": "[email protected]",
  "mobile_number": "+27821234567",
  "from_bank": "FNB",
  "time_created": "2024-01-15T10:30:00Z",
  "is_test": false,
  "signature": "5d41402abc4b2a76b9719d911017c592",
  "notify_url": "https://yourwebsite.co.za/webhook/payment-notification",
  "success_url": "https://yourwebsite.co.za/payment/success",
  "pending_url": "https://yourwebsite.co.za/payment/pending",
  "cancel_url": "https://yourwebsite.co.za/payment/cancelled"
}

The webhook URL will also include a query parameter: ?type=payment

Notes:

  • The payload may contain additional fields not shown in the example above.
  • The top-level recipient_account_code field is always empty; your account code is available in recipient_account.account_code.
  • A notification is also sent when a payment fails; the payload then has status failed and includes an error_message field.

Security & validation

To ensure the webhook is legitimate and the payment is valid, you must perform the following security checks:

1. Verify source IP address

All webhook requests originate from Bob Pay's static IP addresses. Verify that the request comes from one of these IPs:

  • Sandbox environment: 13.245.58.93
  • Production environment: 13.246.100.25
2. Verify the signature

The signature field in the webhook payload is an MD5 hash that you should verify to confirm the data hasn't been tampered with.

Signature calculation:

// Example: Node.js signature verification
const crypto = require("crypto");

// URL-encode a value the same way Bob Pay does (form encoding: spaces become "+")
function queryEscape(value) {
  return encodeURIComponent(value)
    .replace(/%20/g, "+")
    .replace(/[!'()*]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase());
}

function verifySignature(webhookData, accountPassphrase, accountCode) {
  // The top-level recipient_account_code field in the payload is always empty.
  // The signature is calculated over your actual account code, so use your own
  // account code (also available in webhookData.recipient_account.account_code).
  const keyValuePairs = [
    `recipient_account_code=${queryEscape(accountCode)}`,
    `custom_payment_id=${queryEscape(webhookData.custom_payment_id)}`,
    `email=${queryEscape(webhookData.email || "")}`,
    `mobile_number=${queryEscape(webhookData.mobile_number || "")}`,
    `amount=${webhookData.amount.toFixed(2)}`,
    `item_name=${queryEscape(webhookData.item_name || "")}`,
    `item_description=${queryEscape(webhookData.item_description || "")}`,
    `notify_url=${queryEscape(webhookData.notify_url)}`,
    `success_url=${queryEscape(webhookData.success_url)}`,
    `pending_url=${queryEscape(webhookData.pending_url)}`,
    `cancel_url=${queryEscape(webhookData.cancel_url)}`,
  ];
  const signatureString =
    keyValuePairs.join("&") + `&passphrase=${accountPassphrase}`;
  const calculatedSignature = crypto
    .createHash("md5")
    .update(signatureString)
    .digest("hex");
  return calculatedSignature === webhookData.signature;
}

Note: The account passphrase is a secret key associated with your merchant account. This passphrase can be found in your account settings.

3. Verify payment with Bob Pay

After verifying the signature, you should confirm the payment's validity with Bob Pay by calling the validation endpoint with the full webhook payload.

Endpoint: POST /payments/intents/validate

Request body: Send the complete webhook payload you received.

Success response (200 OK): The payment is valid and confirmed.

Error response: A non-200 response is returned when the payment could not be verified or doesn't match.

4. Verify the amount

Ensure the paid_amount matches the expected amount for your order. Note that some payment methods may allow non-exact payments if enabled for your account.

Responding to webhooks

Important: You must return a 200 OK HTTP status code to acknowledge receipt of the webhook. If Bob Pay doesn't receive a 200 OK response, it will retry sending the webhook.

// Example: Express.js webhook endpoint
app.post("/webhook/payment-notification", async (req, res) => {
  try {
    const webhookData = req.body;
    // 1. Verify source IP
    const clientIp = req.ip || req.connection.remoteAddress;
    const allowedIPs = ["13.245.58.93", "13.246.100.25"]; // Sandbox and Production
    if (!allowedIPs.includes(clientIp)) {
      return res.status(403).send("Forbidden");
    }
    // 2. Verify signature
    const isValidSignature = verifySignature(
      webhookData,
      YOUR_PASSPHRASE,
      YOUR_ACCOUNT_CODE
    );
    if (!isValidSignature) {
      return res.status(400).send("Invalid signature");
    }
    // 3. Verify amount
    const expectedAmount = await getOrderAmount(webhookData.custom_payment_id);
    if (webhookData.paid_amount !== expectedAmount) {
      return res.status(400).send("Amount mismatch");
    }
    // 4. Validate with Bob Pay (send full webhook payload)
    const validationResponse = await axios.post(
      "https://api.sandbox.bobpay.co.za/v2/payments/intents/validate",
      webhookData,
      {
        headers: {
          Authorization: `Bearer ${process.env.BOBPAY_API_TOKEN}`,
          "Content-Type": "application/json",
        },
      }
    );
    // 5. Process the payment in your system
    await processOrder(webhookData.custom_payment_id, webhookData);
    // 6. Return 200 OK to acknowledge receipt
    res.status(200).send("OK");
  } catch (error) {
    console.error("Webhook processing error:", error);
    // Still return 200 if you've successfully received and logged the webhook
    // to prevent retries for processing errors
    res.status(200).send("Received");
  }
});

Webhook retry mechanism

If Bob Pay doesn't receive a 200 OK response from your webhook endpoint, it will automatically retry sending the notification using an exponential backoff strategy:

Retry schedule: Bob Pay retries with increasing delays, up to 17 retries over approximately 4 days.

Best practices:

  • Make your webhook endpoint idempotent (able to handle duplicate notifications safely)
  • Use the uuid or custom_payment_id to prevent processing the same payment multiple times
  • Store webhook receipts in your database to track which notifications you've already processed
  • Return 200 OK as quickly as possible; perform time-consuming operations asynchronously

Payment statuses

Status Description
unpaid Payment intent created but not yet paid
paid Payment completed successfully
failed Payment failed or was declined
canceled Payment was cancelled by the customer or system
refund_pending A refund has been initiated but not yet completed
refunded Payment has been fully refunded
partially_refunded Payment has been partially refunded
chargeback A chargeback was raised against the payment
deleted Payment intent was deleted

Troubleshooting

  1. Not receiving webhooks?

    • Ensure your notify_url is publicly accessible (not localhost)
    • Verify your server accepts POST requests
    • Check your firewall allows traffic from Bob Pay's IP addresses
    • Ensure your endpoint returns a 200 OK status code
  2. Receiving duplicate webhooks?

    • Implement idempotency using the uuid field
    • Check if your endpoint is returning non-200 status codes
  3. Signature validation failing?

    • Use your own account code for recipient_account_code — the payload's top-level field is always empty (see recipient_account.account_code)
    • Ensure you're using the correct passphrase
    • Verify you're URL-encoding values correctly (spaces must be encoded as +, not %20)
    • Check that you're using the exact fields in the correct order
    • Ensure you're formatting the amount to 2 decimal places