Payments API flows

Authentication

To authenticate, include the mandatory authentication object, containing the userId, password, and entityId fields, in the request body. The authentication object verifies that the request originated from a valid merchant using a valid payment method for a specific currency.

Payment flow

The Payments API supports both synchronous and asynchronous payment flows:

  • Synchronous flow: In this flow, you send all the required parameters in the request and receive a final transaction status response without requiring any extra customer input. 1Voucher uses a synchronous flow. For this payment flow, you have full control over the user interface that you use to collect the required parameters.

  • Asynchronous flow: In this flow, the transaction enters a pending state before the final state. You must use webhooks and the transaction status endpoint to query the transaction's final status. The two types of asynchronous flows are:

    • Custom user interface: You collect the required customer information using your custom user interface and make the payment request. In the response, you get a second factor (waiting or OTP) page that you must load until the payment completes, for example, Capitec Pay, blink by Emtel, MCB Juice, M-PESA, Mobicred, and so on.
    • Payment service provider user interface: You execute a payment request and receive back a URL which you must load to show the user interface required for the customer to complete the transaction, for example, Peach EFT, Payflex, ZeroPay, RCS cards, Float, and so on. For this flow, you have limited control over the user interface used to collect the required customer information.

The following section describes an asynchronous flow:

Payment flow.

Payment flow.

  1. The merchant initiates the call to create a new transaction by making a request to the /payments endpoint.
  2. The Payments API returns a response with a 000.200.000 result code indicating that it has created the transaction, placed it in a pending state, and is now awaiting payment.
  3. The Payments API sends a webhook to the merchant with the same result code, informing them that the transaction moved from processing to pending. The merchant should expect webhooks whenever the transaction changes status.
  4. The merchant must redirect the customer to the URL in the redirect object. Some payment methods use GET requests, for which you must include the parameters as query strings. Others use POST requests, for which you must include the parameters in the request body as x-www-form-urlencoded content. See example redirect requests below.
  5. The payment method user interface displays to capture the customer's payment information.
  6. The customer submits the required information.
  7. The Payments API redirects back to the URL specified in shopperResultUrl in the original payment request.
  8. The merchant executes the transaction status API request to retrieve the final transaction status.
  9. The Payments API sends a webhook to the merchant notifying them of the outcome of the transaction. For successful transactions, the webhook has the 000.000.000 result code.

Example redirect requests

curl --location 'https://payment.ftapp.co.za/93db1f009fb944b2ab23efde702eaec1?reference=93db1f009fb944b2ab23efde702eaecz'
curl --location 'https://testapi-v2.peachpayments.com/verify/' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'amount=10.00' \
--data-urlencode 'connector=MASTERPASS' \
--data-urlencode 'currency=ZAR' \
--data-urlencode 'transaction=867acdb32cd1451eb5a4b9bb3ac1994z'

📘

See the Postman collection for more redirect examples.

Refund flow

The refund flow uses the same endpoint as the debit flow, but the refund endpoint requires the original Peach unique ID as a path parameter, for example, /payments/{unique_transaction_id}/ and you must set the payment type to RF.

Refund flow.

Refund flow.

  1. The merchant makes a request to the refund endpoint with the Peach unique ID of the original transaction.
  2. For asynchronous refunds, the Payments API returns a pending result code (000.200.000) and the transaction transitions to a final state when the PSP has processed the refund.
    For synchronous refunds, the Payments API returns either a successful or failure result code. The merchant receives a webhook for each transaction status change. The merchant can use the transaction status API call to verify the status of a transaction at any time.

Transaction status flow

🚧

You can send two transaction status requests per minute per transaction.

Transaction status flow.

Transaction status flow.

  1. The merchant sends a transaction status request to the Payments API.
  2. The Payments API responds, with the result code indicating the status of the transaction.

Webhook flow

Webhooks provide updates on any changes in the state of the transaction. The Payments API sends them regardless of whether the transaction succeeded or failed. You must decrypt the encrypted webhook request body to process the transaction data.

📘

In certain circumstances, Peach Payments could update a transaction status using webhooks after a payment session ends. For example, system issues might result in a transaction status being incorrect during the session; when the session ends and the system recovers, Peach Payments sends a webhook with the updated status.

Transaction statuses can change as follows:

  • Pending -> Successful
  • Pending -> Failed
  • Failed -> Successful

Peach Payments cannot guarantee the order of webhooks. For example, if your customer initiates a transaction and Peach Payments sends a pending webhook but you have a system issue that causes the webhook to go into exponential backoff, Peach Payments might send the next webhook before the pending webhook gets retried. The webhook payload contains a timestamp that you can use to identify the correct order of webhooks.

Webhook flow.

Webhook flow.

  1. The merchant receives a webhook with a result code indicating the updated transaction status.
  2. The merchant returns a 200 status code response acknowledging the webhook.

📘

To decrypt the webhook, use the code snippets provided in the Webhook decryption section.

Webhook configuration

The following sections describe how to add, disable, or delete a webhook.

If you're experiencing issues, ensure that you've allowlisted the Peach Payments webhook IPs, and if that doesn't help, contact support.

Add a webhook

Follow these steps to add a webhook:

  1. Log in to your Peach Payments Dashboard.
  2. In the left navigation menu, click Payments API.
  3. In the Webhooks section, click Add webhook URL.
  4. In the Adding webhook URL window that appears, enter your webhook URL, ensuring that your system responds with a 200 status response. Peach Payments sends two webhooks to this URL, the first without data (curl -v -X POST https://www.mysite.co.za/pp-hosted/secure/webhook) and the second with JSON data (curl -v -H "Content-Type: application/json" -X POST -d '{"test":true}' https://www.mysite.co.za/pp-hosted/secure/webhook) if the initial attempt fails. If both attempts fail to return a 200 status, Peach Payments considers the validation unsuccessful.
  5. Click the JSON wrapper toggle to the on position if you prefer JSON wrapped notifications (for example, {"encryptedBody":"[(encrypted) hexadecimal string]"}) instead of plain text.
  6. Click the PII (Personal Identifiable Information) toggle to the on position.

    🚧

    There is a known issue where leaving the PII toggle in the off position results in Peach Payments not sending any webhooks.

  7. Click Add webhook URL.

The webhook URL appears in the Webhooks section with a secret key which you must use to decrypt webhooks.

Disable or delete a webhook

Follow these steps to disable or delete a webhook:

  1. Log in to your Peach Payments Dashboard.
  2. In the left navigation menu, click Payments API.
  3. In the Webhooks section:
    • To disable the webhook, click the Enabled toggle to the off position.
    • To delete the webhook, click the more options icon next to the webhook that you want to delete, then click Delete. In the confirmation window, click Delete.

Peach Payments disables or deletes the webhook.

Webhook decryption

Use the code snippets below to decrypt the encrypted webhook request body.

var crypto = require("crypto");
 
// Data from configuration
var secretFromConfiguration = "247b792c09a5f09b913c7767aef9ad12";
 
// Data from server
var ivfromHttpHeader ="33949c7e78cbfb6c821a5520";
var authTagFromHttpHeader ="0adac3207f38e55cf8693288662345b4";
var httpBody = "0A3471C72D9BE49A8520F79C66BBD9A12FF9";
 
// Convert data to process
var key = new Buffer(secretFromConfiguration, "hex");
var iv = new Buffer(ivfromHttpHeader, "hex");
var authTag = new Buffer(authTagFromHttpHeader, "hex");
var cipherText = new Buffer(httpBody, "hex");
 
// Prepare descryption
var decipher = crypto.createDecipheriv("aes-128-gcm", key, iv);
decipher.setAuthTag(authTag);
 
// Decrypt
var result = decipher.update(cipherText) + decipher.final();
console.log(result);
import os
import binascii
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

# Data from configuration
key_from_configuration = "6bcd3d3e05c10464f281f35790f0a096"  # 16 bytes for AES-128

# Data from server
iv_from_http_header = "86a4c92bb77f83f332be2590"  
auth_tag_from_http_header = "eca9eb3675083db06f830a25e300a681"  
http_body = "0A3471C72D9BE49A8520F79C66BBD9A12FF9"  # ciphertext

# Convert data to process
key = binascii.unhexlify(key_from_configuration)
iv = binascii.unhexlify(iv_from_http_header)
auth_tag = binascii.unhexlify(auth_tag_from_http_header)
cipher_text = binascii.unhexlify(http_body)

# Prepare decryption
decryptor = Cipher(algorithms.AES(key), modes.GCM(iv, auth_tag), backend=default_backend()).decryptor()

# Decrypt
result = decryptor.update(cipher_text) + decryptor.finalize()

# Print the result
print(result.decode('utf-8', errors='ignore'))
<?php
/* Php 7.1 or later */
    $key_from_configuration = "247b792c09a5f09b913c7767aef9ad12";
    $iv_from_http_header = "33949c7e78cbfb6c821a5520";
    $auth_tag_from_http_header = "0adac3207f38e55cf8693288662345b4";
    $http_body = "0A3471C72D9BE49A8520F79C66BBD9A12FF9";
    
    $key = hex2bin($key_from_configuration);
    $iv = hex2bin($iv_from_http_header);
    $auth_tag = hex2bin($auth_tag_from_http_header);
    $cipher_text = hex2bin($http_body);
    
    $result = openssl_decrypt($cipher_text, "aes-128-gcm", $key, OPENSSL_RAW_DATA, $iv, $auth_tag);
    print($result);
    
/* Php prior to 7.1 */
    $key_from_configuration = "247b792c09a5f09b913c7767aef9ad12";
    $iv_from_http_header = "33949c7e78cbfb6c821a5520";
    $auth_tag_from_http_header = "0adac3207f38e55cf8693288662345b4";
    $http_body = "0A3471C72D9BE49A8520F79C66BBD9A12FF9";
    
    $key = hex2bin($key_from_configuration);
    $iv = hex2bin($iv_from_http_header);
    $auth_tag = hex2bin($auth_tag_from_http_header);
    $cipher_text = hex2bin($http_body);
    
    $result = openssl_decrypt($cipher_text, "aes-128-gcm", $key, OPENSSL_RAW_DATA, $iv, $auth_tag);
    print($result);
    
?>
require("openssl")
 
# Convert hexadecimal string
def convert(hex)
    return [hex].pack("H*")
end
 
# Create new decipher
def new_decipher(key, iv)
    cipher = OpenSSL::Cipher.new("aes-128-gcm")
    cipher.decrypt
    cipher.key = key
    cipher.iv = iv
     
    return cipher
end
 
# Data from configuration
key_from_configuration = "247b792c09a5f09b913c7767aef9ad12"
 
# Data from server
iv_from_http_header = "33949c7e78cbfb6c821a5520"
auth_tag_from_http_header = "0adac3207f38e55cf8693288662345b4"
http_body = "0A3471C72D9BE49A8520F79C66BBD9A12FF9"
 
# Convert data to process
key = convert(key_from_configuration)
iv = convert(iv_from_http_header)
auth_tag = convert(auth_tag_from_http_header)
cipher_text = convert(http_body)
 
# Prepare decryption
decipher = new_decipher(key, iv)
decipher.auth_tag = auth_tag
 
# Decrypt
result = decipher.update(cipher_text) + decipher.final
puts result
using System;
using System.Linq;
using System.Text;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
 
namespace DecryptionExample
{
    // You need to install bccrypto-csharp from the BouncyCastle page (https://www.bouncycastle.org/download/bouncy-castle-c/).
    class Program
    {
        static void Main(string[] args)
        {
            string keyFromConfiguration = "247b792c09a5f09b913c7767aef9ad12";
 
            // Data from server
            string ivFromHttpHeader = "000000000000000000000000";
            string authTagFromHttpHeader = "CE573FB7A41AB78E743180DC83FF09BD";
            string httpBody = "0A3471C72D9BE49A8520F79C66BBD9A12FF9";
 
            // Convert data to process
            byte[] key = ToByteArray(keyFromConfiguration);
            byte[] iv = ToByteArray(ivFromHttpHeader);
            byte[] authTag = ToByteArray(authTagFromHttpHeader);
            byte[] encryptedText = ToByteArray(httpBody);
            byte[] cipherText = encryptedText.Concat(authTag).ToArray();
 
            // Prepare decryption
            GcmBlockCipher cipher = new GcmBlockCipher(new AesFastEngine());
            AeadParameters parameters = new AeadParameters(new KeyParameter(key), 128, iv);
            cipher.Init(false, parameters);
 
            // Decrypt
            var plainText = new byte[cipher.GetOutputSize(cipherText.Length)];
            var len = cipher.ProcessBytes(cipherText, 0, cipherText.Length, plainText, 0);
            cipher.DoFinal(plainText, len);
            Console.WriteLine(Encoding.ASCII.GetString(plainText));
        }
 
        static byte[] ToByteArray(string HexString)
        {
            int NumberChars = HexString.Length;
            byte[] bytes = new byte[NumberChars / 2];
            for (int i = 0; i < NumberChars; i += 2)
            {
                bytes[i / 2] = Convert.ToByte(HexString.Substring(i, 2), 16);
            }
            return bytes;
        }
    }
}
import org.bouncycastle.jce.provider.BouncyCastleProvider
 
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import java.security.Security
 
// For JVM-based languages, you might need to install unrestricted policy files provided by Sun Microsystems.
// If you encounter errors like `java.lang.SecurityException: Unsupported keysize or algorithm parameters` or `java.security.InvalidKeyException: Illegal key size`, check the 
// BouncyCastle FAQ page(https://www.bouncycastle.org/about/bouncy-castle-fips-faq/) for guidance.

// If installing the unrestricted policy files isn't an option, try using reflection as a workaround, as explained in this StackOverflow discussion on avoiding the need for unlimited strength JCE policy files(https://stackoverflow.com/questions/1179672/how-to-avoid-installing-unlimited-strength-jce-policy-files-when-deploying-an).
 
class Cipher {
    static void main(String[] args) {
        Security.addProvider(new BouncyCastleProvider())
 
        // Data from configuration
        def keyFromConfiguration = "247b792c09a5f09b913c7767aef9ad12"
 
        // Data from server
        def ivFromHttpHeader = "000000000000000000000000"
        def authTagFromHttpHeader = "CE573FB7A41AB78E743180DC83FF09BD"
        def httpBody = "0A3471C72D9BE49A8520F79C66BBD9A12FF9"
 
        // Convert data to process
        def key = keyFromConfiguration.decodeHex()
        def iv = ivFromHttpHeader.decodeHex()
        def authTag = authTagFromHttpHeader.decodeHex() as Byte[]
        def encryptedText = httpBody.decodeHex() as Byte[]
 
        // Unlike other programming languages, we have to append the auth tag at the end of encrypted text
        def cipherText = encryptedText + authTag as Byte[]
 
        // Prepare decryption
        def keySpec = new SecretKeySpec(key, 0, 16, "AES")
        def cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding")
        cipher.init(javax.crypto.Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv))
 
        // Decrypt
        def result = cipher.doFinal(cipherText)
        println(new String(result, "UTF-8"))
    }
}
import com.google.common.base.Charsets;
import org.apache.commons.lang3.ArrayUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Security;
 
// For Java, you might need to install unrestricted policy files provided by Sun Microsystems.
// If you encounter errors like `java.lang.SecurityException: Unsupported keysize or algorithm parameters` or `java.security.InvalidKeyException: Illegal key size`, check the 
// BouncyCastle FAQ page(https://www.bouncycastle.org/about/bouncy-castle-fips-faq/) for guidance.

// If installing the unrestricted policy files isn't an option, try using reflection as a workaround, as explained in this StackOverflow discussion on avoiding the need for unlimited strength JCE policy files(https://stackoverflow.com/questions/1179672/how-to-avoid-installing-unlimited-strength-jce-policy-files-when-deploying-an).
public class Decryption
{
    public static void main(String[] args) throws Exception
    {
        Security.addProvider(new BouncyCastleProvider());
 
        // Data from configuration
        String keyFromConfiguration = "247b792c09a5f09b913c7767aef9ad12";
 
        // Data from server
        String ivFromHttpHeader = "000000000000000000000000";
        String authTagFromHttpHeader = "CE573FB7A41AB78E743180DC83FF09BD";
        String httpBody = "0A3471C72D9BE49A8520F79C66BBD9A12FF9";
 
        // Convert data to process
        byte[] key = DatatypeConverter.parseHexBinary(keyFromConfiguration);
        byte[] iv = DatatypeConverter.parseHexBinary(ivFromHttpHeader);
        byte[] authTag = DatatypeConverter.parseHexBinary(authTagFromHttpHeader);
        byte[] encryptedText = DatatypeConverter.parseHexBinary(httpBody);
 
        // Unlike other programming languages, we have to append the auth tag at the end of encrypted text in Java
        byte[] cipherText = ArrayUtils.addAll(encryptedText, authTag);
 
        // Prepare decryption
        SecretKeySpec keySpec = new SecretKeySpec(key, 0, 16, "AES");
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
 
        // Decrypt
        byte[] bytes = cipher.doFinal(cipherText);
        System.out.println(new String(bytes, Charsets.UTF_8));
    }
}
import java.nio.charset.Charset;
import java.security.Security
import java.security.SecureRandom;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider
 
// For JVM-based languages, you might need to install unrestricted policy files provided by Sun Microsystems.
// If you encounter errors like `java.lang.SecurityException: Unsupported keysize or algorithm parameters` or `java.security.InvalidKeyException: Illegal key size`, check the 
// BouncyCastle FAQ page(https://www.bouncycastle.org/about/bouncy-castle-fips-faq/) for guidance.

// If installing the unrestricted policy files isn't an option, try using reflection as a workaround, as explained in this StackOverflow discussion on avoiding the need for unlimited strength JCE policy files(https://stackoverflow.com/questions/1179672/how-to-avoid-installing-unlimited-strength-jce-policy-files-when-deploying-an).
 
object Cipher {
  def main(args: Array[String]) = {
    Security.addProvider(new BouncyCastleProvider())
 
    // Data from configuration
    val keyFromConfiguration = "247b792c09a5f09b913c7767aef9ad12"
     
    // Data from server
    val ivFromHttpHeader = "000000000000000000000000"
    val authTagFromHttpHeader = "CE573FB7A41AB78E743180DC83FF09BD"
    val httpBody = "0A3471C72D9BE49A8520F79C66BBD9A12FF9"
     
    // Convert data to process
    val key = hexToBin(keyFromConfiguration)
    val iv = hexToBin(ivFromHttpHeader)
    val authTag = hexToBin(authTagFromHttpHeader)
    val encryptedText = hexToBin(httpBody)
     
    // Unlike other programming languages, we have to append the auth tag at the end of encrypted text
    val cipherText = encryptedText ++ authTag
     
    // Prepare decryption
    val keySpec = new SecretKeySpec(key, 0, 16, "AES")
    val cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding")
    cipher.init(javax.crypto.Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv))
 
    // Decrypt
    val result = cipher.doFinal(cipherText)
    println(new String(result, "UTF-8"))
  }
 
  def hexToBin(hex: String) : Array[Byte] = {
    return DatatypeConverter.parseHexBinary(hex)
  }
}

Webhook events

Peach Payments sends webhooks for certain events, including, but not limited to:

  • Pending
  • Successful
  • Cancelled
  • Failed

After decryption, the webhook payload resembles the following:

{
  "id":"02f2ef804c4f4713ab053661cba98d4z",
  "referencedId":"",
  "paymentType":"DB",
  "paymentBrand":"PEACHEFT",
  "amount":"1.0",
  "merchantTransactionId":"EFTTestdb7532d8d",
  "merchantInvoiceId":null,
  "merchantAccountId":"28f0a55c50e911eb88ef02de7a5a0a6z",
  "descriptor":"",
  "currency":"ZAR",
  "presentationAmount":"1.0",
  "presentationCurrency":"ZAR",
  "result":{
     "code":"000.200.000",
     "description":"transaction pending"
  },
  "resultDetails":{
     "clearingInstituteName":"EFT",
     "ExtendedDescription":"Payment process started.",
     "AcquirerResponse":"PENDING"
  },
  "connectorTxID1":null,
  "authentication":{
     "entityId":"8ac7a4ca77a64c9c0177af52972c13bz"
  },
  "card":{
     "bin":null,
     "last4Digits":null,
     "holder":null,
     "type":null,
     "expiryMonth":null,
     "expiryYear":null
  },
  "timestamp":"2023-07-20T11:12:26.510635Z",
  "customer":{
     "givenName":"Grace",
     "surname":"Nkosi",
     "merchantCustomerId":null,
     "sex":"",
     "mobile":null,
     "email":null,
     "status":null,
     "phone":null
  },
  "shipping":{
     "street1":null,
     "street2":null,
     "city":null,
     "country":null,
     "state":null,
     "postcode":null,
     "company":null
  },
  "billing":{
     "street1":null,
     "street2":null,
     "city":null,
     "sex":"",
     "country":null,
     "state":null,
     "postcode":null,
     "company":null
  },
  "shopify":{
     "orderId":null,
     "accountId":null,
     "signature":null,
     "testMode":null
  },
  "bankAccount":{
     "holder":null,
     "bankName":null,
     "bankCode":null
  },
  "recon":{
     "authCode":null,
     "ciMerchantNumber":null,
     "resultCode":null,
     "rrn":null,
     "stan":null
  },
  "customParameters":{
     "PEACH_MERCHANT_ID":"e098a59e50e411eb88ef02de7a5a0a6z"
  }
}
{
  "id":"02f2ef804c4f4713ab053661cba98d4z",
  "referencedId":"",
  "paymentType":"DB",
  "paymentBrand":"PEACHEFT",
  "amount":"1.0",
  "merchantTransactionId":"EFTTestdb7532d8d",
  "merchantInvoiceId":null,
  "merchantAccountId":"28f0a55c50e911eb88ef02de7a5a0a6z",
  "descriptor":"",
  "currency":"ZAR",
  "presentationAmount":"1.0",
  "presentationCurrency":"ZAR",
  "result":{
     "code":"000.000.000",
     "description":"Transaction succeeded'"
  },
  "resultDetails":{
     "clearingInstituteName":"EFT",
     "ExtendedDescription":"n/a",
     "AcquirerResponse":1
  },
  "connectorTxID1":"105035601",
  "authentication":{
     "entityId":"8ac7a4ca77a64c9c0177af52972c13bz"
  },
  "card":{
     "bin":null,
     "last4Digits":null,
     "holder":null,
     "type":null,
     "expiryMonth":null,
     "expiryYear":null
  },
  "timestamp":"2023-07-20T11:17:33.874611Z",
  "customer":{
     "givenName":"Grace",
     "surname":"Nkosi",
     "merchantCustomerId":null,
     "sex":"",
     "mobile":null,
     "email":null,
     "status":null,
     "phone":null
  },
  "shipping":{
     "street1":null,
     "street2":null,
     "city":null,
     "country":null,
     "state":null,
     "postcode":null,
     "company":null
  },
  "billing":{
     "street1":null,
     "street2":null,
     "city":null,
     "sex":"",
     "country":null,
     "state":null,
     "postcode":null,
     "company":null
  },
  "shopify":{
     "orderId":null,
     "accountId":null,
     "signature":null,
     "testMode":null
  },
  "bankAccount":{
     "holder":null,
     "bankName":null,
     "bankCode":null
  },
  "recon":{
     "authCode":null,
     "ciMerchantNumber":null,
     "resultCode":null,
     "rrn":null,
     "stan":null
  },
  "customParameters":{
     "PEACH_MERCHANT_ID":"e098a59e50e411eb88ef02de7a5a0a6z"
  }
}
{
  "id":"aa4751285ca048b5b9516beb94b6cd5z",
  "referencedId":"",
  "paymentType":"DB",
  "paymentBrand":"PEACHEFT",
  "amount":"1.0",
  "merchantTransactionId":"EFTTestdb7532d8d",
  "merchantInvoiceId":null,
  "merchantAccountId":"28f0a55c50e911eb88ef02de7a5a0a6z",
  "descriptor":"",
  "currency":"ZAR",
  "presentationAmount":"1.0",
  "presentationCurrency":"ZAR",
  "result":{
     "code":"100.396.101",
     "description":"Cancelled by user"
  },
  "resultDetails":{
     "clearingInstituteName":"EFT",
     "ExtendedDescription":"Aborted during payment",
     "AcquirerResponse":0
  },
  "connectorTxID1":"105037083",
  "authentication":{
     "entityId":"8ac7a4ca77a64c9c0177af52972c13bz"
  },
  "card":{
     "bin":null,
     "last4Digits":null,
     "holder":null,
     "type":null,
     "expiryMonth":null,
     "expiryYear":null
  },
  "timestamp":"2023-07-20T11:30:16.445945Z",
  "customer":{
     "givenName":"Grace",
     "surname":"Nkosi",
     "merchantCustomerId":null,
     "sex":"",
     "mobile":null,
     "email":null,
     "status":null,
     "phone":null
  },
  "shipping":{
     "street1":null,
     "street2":null,
     "city":null,
     "country":null,
     "state":null,
     "postcode":null,
     "company":null
  },
  "billing":{
     "street1":null,
     "street2":null,
     "city":null,
     "sex":"",
     "country":null,
     "state":null,
     "postcode":null,
     "company":null
  },
  "shopify":{
     "orderId":null,
     "accountId":null,
     "signature":null,
     "testMode":null
  },
  "bankAccount":{
     "holder":null,
     "bankName":null,
     "bankCode":null
  },
  "recon":{
     "authCode":null,
     "ciMerchantNumber":null,
     "resultCode":null,
     "rrn":null,
     "stan":null
  },
  "customParameters":{
     "PEACH_MERCHANT_ID":"e098a59e50e411eb88ef02de7a5a0a6z"
  }
}
{
  "id":"2d3014384d1b4d53b94203cf9e3e04fz",
  "referencedId":"",
  "paymentType":"DB",
  "paymentBrand":"PEACHEFT",
  "amount":"1.0",
  "merchantTransactionId":"EFTTestdb7532d8d",
  "merchantInvoiceId":null,
  "merchantAccountId":"28f0a55c50e911eb88ef02de7a5a0a6z",
  "descriptor":"",
  "currency":"ZAR",
  "presentationAmount":"1.0",
  "presentationCurrency":"ZAR",
  "result":{
     "code":"800.100.152",
     "description":"transaction declined by authorization system"
  },
  "resultDetails":{
     "clearingInstituteName":"EFT",
     "ExtendedDescription":"Amount daily limit reached",
     "AcquirerResponse":"0"
  },
  "connectorTxID1":null,
  "authentication":{
     "entityId":"8ac7a4ca77a64c9c0177af52972c13bz"
  },
  "card":{
     "bin":null,
     "last4Digits":null,
     "holder":null,
     "type":null,
     "expiryMonth":null,
     "expiryYear":null
  },
  "timestamp":"2023-07-20T11:37:59.649483Z",
  "customer":{
     "givenName":"Grace",
     "surname":"Nkosi",
     "merchantCustomerId":null,
     "sex":"",
     "mobile":null,
     "email":null,
     "status":null,
     "phone":null
  },
  "shipping":{
     "street1":null,
     "street2":null,
     "city":null,
     "country":null,
     "state":null,
     "postcode":null,
     "company":null
  },
  "billing":{
     "street1":null,
     "street2":null,
     "city":null,
     "sex":"",
     "country":null,
     "state":null,
     "postcode":null,
     "company":null
  },
  "shopify":{
     "orderId":null,
     "accountId":null,
     "signature":null,
     "testMode":null
  },
  "bankAccount":{
     "holder":null,
     "bankName":null,
     "bankCode":null
  },
  "recon":{
     "authCode":null,
     "ciMerchantNumber":null,
     "resultCode":null,
     "rrn":null,
     "stan":null
  },
  "customParameters":{
     "PEACH_MERCHANT_ID":"e098a59e50e411eb88ef02de7a5a0a6z"
  }
}

Webhook retry mechanism

Peach Payments expects a 200 HTTP for successful webhook delivery and a non-200 HTTP status code for failures.

For an unsuccessful response, Peach Payments retries the webhook for seven days or until a successful acknowledgement (200 HTTP) occurs.

Peach Payments uses exponential backoff logic for retries, with the interval between retries being:

  • 1 minute
  • 2 minutes
  • 4 minutes
  • 8 minutes
  • 15 minutes
  • 30 minutes
  • 1 hour
  • 6 hours, every day until seven days have passed since the first attempt

Example requests and responses

For sample requests and responses, see the interactive API playground or use the Peach Payments Postman collection:

Run in Postman