Developers

Introducing the official Pandascrow SDK for PHP

The official Pandascrow PHP SDK is now on Packagist. Create escrows, send payouts and verify webhooks from Laravel or plain PHP with one Composer install.

Photo of Precious Tom

Precious Tom

3 min read

Image of Introducing the official Pandascrow SDK for PHP

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.

Warning

Use 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:

PropertyWhat it does
escrowOne-time, milestone and recurring escrows, releases, disputes
wallet and payoutsBalances, transactions, settlement accounts, payouts
bankBank list, account validation, NGN transfers
virtualAccountsDynamic and static accounts to collect transfers
invoicesCreate, pay and cancel invoices
paymentLinksShareable links with custom slugs
kycBVN, 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');
});
Tip

Always 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

  1. Create a free account and get your sandbox key from the dashboard.
  2. Run composer require circlepandaa/pandascrow-php-sdk.
  3. 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.

  • PHP
  • SDK
  • Laravel
  • Composer
  • Escrow API
  • Webhooks
  • Developers