Handling Transaction Callbacks
When you initiate a transaction via the Transactions endpoint and provide a callbackURL
, our system will send the transaction result to this URL via a POST request. You need to set up an endpoint on your server to receive and process this callback.
For security, each callback request includes an Authorization
header containing a unique Bearer token that you received when setting up your account or callback configuration. The samples below demonstrate how to create an endpoint that can receive this POST request, validate the Bearer token to ensure the callback is from our system, and then read the incoming request body, which contains the TransactionResult
data as a JSON payload.
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
using System.Text;
namespace PayArcConnectApi.Controllers {
[Route("[controller]")]
public class CallbackController : Controller {
[HttpPost]
public async Task<IActionResult> CallbackResponse() {
StringValues authHeader;
if (!Request.Headers.TryGetValue("Authorization", out authHeader)
|| StringValues.IsNullOrEmpty(authHeader)) {
return Unauthorized("Authorization header is missing.");
}
string authHeaderValue = authHeader.ToString();
if (!authHeaderValue.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) {
return Unauthorized("Authorization header must start with Bearer.");
}
string token = authHeaderValue.Substring("Bearer ".Length).Trim();
if (token != YOUR_BEARER_TOKEN) {
return Unauthorized("Invalid token.");
}
try {
using var reader = new StreamReader(HttpContext.Request.Body, Encoding.UTF8);
string requestBody = await reader.ReadToEndAsync();
return Ok(requestBody);
} catch (IOException ex) {
Console.Error.WriteLine($"Failed to read stream from callback body: {ex.Message}");
return BadRequest("Invalid response format.");
} catch (Exception ex) {
Console.Error.WriteLine($"An error occurred processing callback: {ex.Message}");
return StatusCode(500, "An internal error occurred.");
}
}
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/callback")
public class CallbackServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || authHeader.isEmpty()) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Authorization header is missing.");
return;
}
if (!authHeader.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Authorization header must start with Bearer.");
return;
}
String token = authHeader.substring("Bearer ".length()).trim();
if (token != YOUR_BEARER_TOKEN) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid token.");
return;
}
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
StringBuilder requestBodyBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"))) {
String line;
while ((line = reader.readLine()) != null) {
requestBodyBuilder.append(line);
}
} catch (IOException ex) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write("Invalid request format.");
return;
} catch (Exception ex) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("An internal error occurred.");
return;
}
String requestBody = requestBodyBuilder.toString();
if (requestBody == null || requestBody.trim().isEmpty()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write("Empty request body.");
return;
}
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write(requestBody);
}
}
async function callbackResponse(req, res) {
try {
const authHeader = req.headers['authorization'];
if (!authHeader) {
res.writeHead(401, { 'Content-Type': 'text/plain' });
res.end('Authorization header is missing.');
return;
}
if (!authHeader.startsWith('Bearer ')) {
res.writeHead(401, { 'Content-Type': 'text/plain' });
res.end('Authorization header must start with Bearer.');
return;
}
const token = authHeader.substring('Bearer '.length).trim();
if (token != YOUR_BEARER_TOKEN) {
res.writeHead(401, { 'Content-Type': 'text/plain' });
res.end('Invalid token.');
}
let requestBody = '';
for await (const chunk of req) {
requestBody += chunk.toString();
}
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(requestBody);
} catch (error) {
res.writeHead(400, { 'Content-Type': 'text/plain' });
res.end('Invalid request format.');
}
}
<?php
header('Content-Type: text/plain');
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (empty($authHeader)) {
http_response_code(401);
echo "Authorization header is missing.";
exit;
}
if (!str_starts_with($authHeader, 'Bearer ')) {
http_response_code(401);
echo "Authorization header must start with Bearer.";
exit;
}
$token = substr($authHeader, strlen('Bearer '));
if ($token != YOUR_BEARER_TOKEN) {
http_response_code(401);
echo "Invalid token.";
exit;
}
$requestBody = file_get_contents('php://input');
if ($requestBody === false) {
http_response_code(400);
echo "Invalid request format.";
exit;
}
if (trim($requestBody) === '') {
http_response_code(400);
echo "Empty request body.";
exit;
}
http_response_code(200);
echo $requestBody;
?>
from flask import Flask, request, Response
app = Flask(__name__)
@app.route('/callback', methods=['POST'])
def callback_response():
try:
auth_header = request.headers.get('Authorization')
if not auth_header:
return Response("Authorization header is missing.", status=401)
if not auth_header.startswith('Bearer '):
return Response("Authorization header must start with Bearer.", status=401)
token = auth_header[len('Bearer '):].strip()
if token != YOUR_BEARER_TOKEN:
return Response("Invalid token.", status=401)
request_body = request.data.decode('utf-8')
if not request_body:
return Response("Empty request body.", status=400)
return Response(request_body, status=200, mimetype='text/plain')
except Exception as e:
return Response("An internal error occurred.", status=500)
import * as http from 'http';
async function callbackResponse(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
try {
const authHeader = req.headers['authorization'];
if (!authHeader) {
res.writeHead(401, { 'Content-Type': 'text/plain' });
res.end('Authorization header is missing.');
return;
}
if (!authHeader.startsWith('Bearer ')) {
res.writeHead(401, { 'Content-Type': 'text/plain' });
res.end('Authorization header must start with Bearer.');
return;
}
const token = authHeader.substring('Bearer '.length).trim();
if (token != YOUR_BEARER_TOKEN) {
res.writeHead(401, { 'Content-Type': 'text/plain' });
res.end('Invalid token.');
}
let requestBody = '';
for await (const chunk of req) {
requestBody += chunk.toString();
}
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(requestBody);
} catch (error: any) {
res.writeHead(400, { 'Content-Type': 'text/plain' });
res.end('Invalid request format.');
}
}
Callback Response
The callback you receive will contain a JSON object with the following top-level structure:
{
"PaxResponse": "string",
"ErrorCode": integer,
"ErrorMessage": "string"
}
ErrorCode
: This field indicates the status of the payment. An error code greater than 0 indicates that the payment was not successfully processed.ErrorMessage
: Provides a human-readable message related to theErrorCode
. IfErrorCode
is 0, this will be empty.PaxResponse
: Response object generated from the PAX terminal after a transaction. It may be null if there was an error.