The official Pandascrow PHP SDK is available on Packagist as circlepandaa/pandascrow-php-sdk. It covers the whole v3 API: escrow, wallets, payouts, bank transfers, virtual accounts, invoices, payment links, KYC and webhook verification.
A lot of the web still runs on PHP. Marketplaces on Laravel, stores on WooCommerce, platforms built years ago and still making money. If that's your stack, you can now add escrow without writing your own HTTP client, response checks or signature code.
Install the PHP SDK
composer require circlepandaa/pandascrow-php-sdk
It needs PHP 7.4 or newer with the curl and json extensions, which almost every host already has. There are no other dependencies, so it won't fight with the packages you already use.
WarningUse the SDK on your server only, and keep your API key in an environment variable. The key gives full access to your Pandascrow account.
Create your first escrow
Get your API key from Dashboard → Developers. The client starts in sandbox, so nothing you do while testing moves real money.
<?php
require 'vendor/autoload.php';
$ps = new Pandascrow\Client(getenv('PANDASCROW_API_KEY')); // sandbox by default
$escrow = $ps->escrow->createOnetime([
'uuid' => $uuid, // the Pandascrow user you're acting for
'initiator_id' => $uuid,
'initiator_role' => 'seller', // buyer | seller | broker
'title' => 'Apartment 4B rent',
'description' => '12-month rent, 12 Admiralty Way',
'currency' => 'NGN',
'amount' => 350000,
'buyer_details' => ['name' => 'Tenant', 'email' => 'tenant@example.com'],
]);
header('Location: ' . $escrow['payment_url']); // send the buyer to fund it
The buyer pays through payment_url, we hold the money, and it's released to the seller once the buyer confirms delivery. Every method returns the data object from the API as a plain PHP array.
When you're ready for real payments, pass your live key and turn sandbox off:
$ps = new Pandascrow\Client(getenv('PANDASCROW_API_KEY'), ['sandbox' => false]);
What the PHP SDK covers
Each part of the API has its own property on the client:
| Property | What it does |
|---|---|
escrow | One-time, milestone and recurring escrows, releases, disputes |
wallet and payouts | Balances, transactions, settlement accounts, payouts |
bank | Bank list, account validation, NGN transfers |
virtualAccounts | Dynamic and static accounts to collect transfers |
invoices | Create, pay and cancel invoices |
paymentLinks | Shareable links with custom slugs |
kyc | BVN, NIN, CAC, TIN, NUBAN, phone and driver's licence lookups |
For anything we haven't wrapped yet, request() sends the call with your key and base URL already set:
$ps->request('GET', '/clients', ['uuid' => $uuid]);
Milestone projects take the stages up front, and the buyer funds them one at a time:
$ps->escrow->createMilestone([
'uuid' => $uuid, 'initiator_id' => $uuid, 'initiator_role' => 'buyer',
'title' => 'Website build', 'description' => 'Company website', 'currency' => 'NGN',
'seller_details' => ['name' => 'Dev Studio', 'email' => 'studio@example.com'],
'deposit_option' => 'milestone',
'milestones' => [
['title' => 'Design', 'amount' => 150000],
['title' => 'Build', 'amount' => 350000],
],
]);
Verify webhooks in Laravel or plain PHP
We sign every webhook with HMAC-SHA256 of the raw request body, using your secret key, and send the signature in the X-Pandascrow-Signature header. Webhook::verify() checks the signature, rejects events older than five minutes to stop replays, and returns the event as an array.
In plain PHP it reads the body and header for you:
try {
$event = Pandascrow\Webhook::verify(getenv('PANDASCROW_SECRET_KEY'));
} catch (Pandascrow\PandascrowException $e) {
http_response_code(400);
exit;
}
if ($event['event'] === 'escrow.paid') {
// mark the order as funded
}
http_response_code(200);
In Laravel, pass the raw body and header from the request:
use Illuminate\Http\Request;
use Pandascrow\PandascrowException;
use Pandascrow\Webhook;
Route::post('/webhooks/pandascrow', function (Request $request) {
try {
$event = Webhook::verify(
config('services.pandascrow.secret'),
$request->getContent(),
$request->header('X-Pandascrow-Signature')
);
} catch (PandascrowException $e) {
return response('invalid signature', 400);
}
// handle $event['event'] and $event['data']
return response('ok');
});
TipAlways pass the raw body string, not a decoded and re-encoded array. Re-encoding changes the bytes, and the signature won't match. Also exclude the webhook route from CSRF protection.
Events include escrow.created, escrow.paid, escrow.completed, escrow.milestone.completed, escrow.dispute.opened, invoice.paid and wallet.deposit.success. The full list is in the webhooks reference.
Keep your own IDs on every record
Escrows, invoices and virtual accounts accept a metadata array. We store it as you sent it, return it when you fetch the record, and include it in every webhook for that record. You can match a Pandascrow escrow to your order without a lookup table.
$ps->escrow->createOnetime([/* ...details */ 'metadata' => ['order_id' => 'A-1001']]);
// later, in your webhook handler
$event['data']['metadata']['order_id']; // "A-1001"
Errors you can act on
Every failure throws a PandascrowException, whether it's a network error, an HTTP error, an invalid response or a response with status: false. You get the message, the HTTP status, the full response and, where we have one, a link to the right page in the docs.
try {
$ps->escrow->complete($uuid, $escrowId, $otp);
} catch (Pandascrow\PandascrowException $e) {
echo $e->getMessage(); // "Invalid OTP"
echo $e->httpStatus; // 400
echo $e->docUrl; // link to the API reference
}
Bank transfers take an idempotency key, so retrying after a timeout can't send the money twice:
$ps->bank->transfer($uuid, 25000, [
'account_number' => '0123456789', 'account_name' => 'Ada Obi', 'bank_code' => '058',
], 'NGN', ['idempotency_key' => 'payout-7781']);
Upgrading from 1.0.0
Version 1.0.0 was an early draft that didn't match the v3 API. If you installed it, update to the latest release and replace your calls with the examples above:
composer require circlepandaa/pandascrow-php-sdk:^1.2
The rewrite also fixes webhook verification. 1.0.0 re-encoded the payload before checking the signature, so valid events containing URLs or non-ASCII text were rejected. The full list of changes is in the changelog on GitHub.
Other ways to integrate
Running a WooCommerce store? The Pandascrow WordPress plugin adds escrow checkout without code. JavaScript teams can use the Node.js SDK on npm, and the Pandascrow CLI lets you create escrows and test webhooks from your terminal.
Start building
- Create a free account and get your sandbox key from the dashboard.
- Run
composer require circlepandaa/pandascrow-php-sdk. - Follow the quick start on our developer page, or read the API reference.
The source is on GitHub under the MIT licence. If something's missing or broken, open an issue there or email hi@pandascrow.io.
New to escrow? See how Pandascrow escrow protects both sides of a deal.
