Tranzak Docs
tranzak.co Dashboard
Docs  /  Accept payments

Customer return URL

What happens when the customer returns to your site after paying, how to verify the signed return URL, and why it never replaces server-side confirmation.

For redirect methods, the customer's browser is sent back to the return URL of your website after the payment page. You set it in the dashboard, under Developer → Websites.

What Tranzak sends

The browser is redirected with query parameters:

https://yoursite.com/pay/return?transaction_id=17843268616389&status=success&amount=500.00&currency=HTG&timestamp=1791122041&reference=ORDER-4821&signature=<hex>
Parameter Description
transaction_id The Tranzak transaction identifier.
status success when the payment is completed, otherwise failed.
amount, currency The amount and currency of the payment.
timestamp Unix time of the redirect.
reference Your reference, if you sent one.
signature HMAC-SHA256 signature of the URL.

Warning failed also appears when the payment is not confirmed yet, for example a NatCash payment still pending. Treat this page as "payment in progress, checking", then confirm on your server. See Confirm a payment.

Verify the signature

The signed message is the query string exactly as received, without the final &signature=<hex>, in the original parameter order. Do not sort the parameters.

signature = hex( HMAC-SHA256( key = your website's webhook secret, message = the query string before &signature= ) )
$query = $_SERVER['QUERY_STRING'];
$pos = strrpos($query, '&signature=');
$signed = substr($query, 0, $pos);
$received = substr($query, $pos + strlen('&signature='));

$expected = hash_hmac('sha256', $signed, getenv('TRANZAK_WEBHOOK_SECRET'));
if (! hash_equals($expected, $received)) {
    http_response_code(403);
    exit('Invalid signature');
}
const crypto = require('crypto');

function verifyReturn(rawQuery, secret) {
  const marker = '&signature=';
  const pos = rawQuery.lastIndexOf(marker);
  if (pos < 0) return false;
  const signed = rawQuery.slice(0, pos);
  const received = rawQuery.slice(pos + marker.length);
  const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex');
  return received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}
import hmac, hashlib

def verify_return(raw_query: str, secret: str) -> bool:
    marker = "&signature="
    pos = raw_query.rfind(marker)
    if pos < 0:
        return False
    signed, received = raw_query[:pos], raw_query[pos + len(marker):]
    expected = hmac.new(secret.encode(), signed.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received)

A valid signature only proves the URL came from Tranzak. It does not replace the server-side confirmation.

  1. Verify the signature, and ignore the request if it is invalid.
  2. Show a temporary message such as "Verifying your payment…".
  3. Read the payment from your server: GET /api/gateway/v1/payments/{transaction_id}.
  4. Let the webhook perform the final update of your order.

Local development

The redirect is performed by the customer's browser, so a return URL such as http://localhost:3000/pay/return works while you test.