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¤cy=HTG×tamp=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
failedalso 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.
Recommended flow
- Verify the signature, and ignore the request if it is invalid.
- Show a temporary message such as "Verifying your payment…".
- Read the payment from your server:
GET /api/gateway/v1/payments/{transaction_id}. - 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.