Language guides
PHP integration
A complete PHP setup for shared hosting, Laravel or a custom checkout: create a charge, verify the return and validate webhooks.
10 min read
What you will build
Three small files are enough: config.php with your key and a request helper, create-charge.php that starts the payment, and webhook.php that confirms it. The same helper works from Laravel, CodeIgniter, a WordPress theme or plain PHP.
- PHP 7.4 or newer with cURL and the JSON extension enabled.
- A brand API key from Developers → API keys and the webhook secret from the same page.
- HTTPS on your site. Webhooks must be reachable from the public internet.
1. Configuration
<?php
// keep this file outside the public web root when possible
const TF_API_BASE = "https://your-domain.com"; // the TakaFlow server
const TF_SITE = "https://shop.example.com"; // your own website
const TF_API_KEY = "tf_live_xxxxxxxxxxxxxxxx";
const TF_WEBHOOK_SECRET = "whsec_xxxxxxxxxxxxxxxx";
function tf_request(string $path, array $body): array {
$ch = curl_init(TF_API_BASE . $path);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 20,
CURLOPT_HTTPHEADER => [
"RT-TAKAFLOW-API-KEY: " . TF_API_KEY,
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode($body),
]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$json = json_decode((string) $raw, true);
if ($status >= 400 || !is_array($json)) {
throw new RuntimeException($json["message"] ?? "TakaFlow request failed");
}
return $json;
}2. Create a charge at checkout
Replace the order lookup with your own cart or orders table. Store the returned invoice_number next to the order before redirecting — the success page and webhook both need it.
<?php
require __DIR__ . "/config.php";
session_start();
$orderId = create_local_order($_POST); // your own function
$charge = tf_request("/api/charge", [
"full_name" => $_POST["name"],
"email" => $_POST["email"],
"phone" => $_POST["phone"] ?? "",
"amount" => (float) $_POST["amount"],
"description" => "Order #" . $orderId,
"metadata" => ["order_id" => $orderId],
"redirect_url" => TF_SITE . "/thanks.php",
"cancel_url" => TF_SITE . "/cart.php",
]);
save_invoice_number($orderId, $charge["invoice_number"]);
$_SESSION["tf_invoice"] = $charge["invoice_number"];
header("Location: " . $charge["payment_url"]);
exit;The customer pays your bKash, Nagad or Rocket number on the hosted checkout and submits the TrxID there. You never handle card data or MFS credentials.
3. Verify when the customer returns
The redirect is a hint, not proof. Ask the API for the final invoice state before you mark anything paid, and compare metadata.order_id with the order you are fulfilling.
<?php
require __DIR__ . "/config.php";
session_start();
$invoice = $_GET["invoice_id"] ?? $_SESSION["tf_invoice"] ?? "";
if ($invoice === "") {
exit("Missing invoice reference.");
}
$state = tf_request("/api/verify-payment", ["invoice_id" => $invoice]);
if ($state["payment_status"] === "COMPLETED") {
mark_order_paid($state["metadata"]["order_id"], $state["transaction_id"]);
echo "Payment received. Thank you!";
} elseif ($state["payment_status"] === "PENDING") {
echo "We are verifying your transaction. This page refreshes automatically.";
} else {
echo "This payment is not complete yet.";
}4. Receive the webhook
Create the endpoint in Developers → Webhooks and paste the URL of this script. Read the raw body before any framework touches it, verify the HMAC, acknowledge with 200, then finish your own work.
Use the X-TakaFlow-Delivery header as an idempotency key so a retried delivery never fulfils an order twice.
<?php
require __DIR__ . "/config.php";
$raw = file_get_contents("php://input");
$expected = "sha256=" . hash_hmac("sha256", $raw, TF_WEBHOOK_SECRET);
$received = $_SERVER["HTTP_X_TAKAFLOW_SIGNATURE"] ?? "";
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit("invalid signature");
}
$event = json_decode($raw, true);
$deliveryId = $_SERVER["HTTP_X_TAKAFLOW_DELIVERY"] ?? "";
http_response_code(200); // acknowledge first
if (function_exists("fastcgi_finish_request")) {
fastcgi_finish_request(); // let PHP keep working
}
if (($event["event"] ?? "") === "payment.completed") {
if (already_processed($deliveryId)) exit; // your idempotency table
mark_order_paid(
$event["metadata"]["order_id"],
$event["transaction_id"]
);
}5. Laravel and other frameworks
- Add the webhook route to the CSRF exception list, or register it in routes/api.php — the signature check is the authentication.
- In a controller use $request->getContent() for the raw body, not $request->all().
- Http::withHeaders(["RT-TAKAFLOW-API-KEY" => config("services.takaflow.key")])->post(...) replaces the cURL helper.
- Wrap charge creation in a try/catch and show a payment-unavailable message instead of a stack trace.
- Queue fulfilment work (queue jobs, not inline HTTP calls) so the webhook returns in milliseconds.
6. Go-live checklist
- Keys live in environment variables or config, never in a public repository.
- Webhook URL is HTTPS and answers 200 to a test delivery from the dashboard.
- The success page verifies with /api/verify-payment and checks metadata.order_id.
- Order fulfilment is idempotent on X-TakaFlow-Delivery.
- A ৳10 test payment from a second phone completes end to end.