PHP SDK watenga/watenga-php
PHP 7.4 or later with ext-curl and ext-json.
Installation
composer require watenga/watenga-phpBefore the first release
# composer.json
"repositories": [{ "type": "path", "url": "../watenga-payment-network/packages/watenga-php" }]
composer require watenga/watenga-php:@devTake a test payment
Use a test secret key (sk_test_…) from the developer console. Test keys never move money: use the test cards and wallet numbers.
use Watenga\Watenga;
$watenga = new Watenga(getenv('WATENGA_SECRET_KEY')); // sk_test_...
$checkout = $watenga->checkout->create([
'amount' => '25.00',
'currency' => 'USD',
'merchant_transaction_id' => 'ORD-1001',
'return_url' => 'https://shop.example/return',
]);
header('Location: ' . $checkout['payment_url']);Receive the webhook
Verify the Watenga-Signature header against the raw request body before you trust an event. While you rotate the endpoint's signing secret, pass both secrets: either one verifies. On your machine, watenga listen --forward-to <your URL> (the CLI) forwards test events signed with a session secret.
use Watenga\SignatureVerificationException;
use Watenga\Webhook;
try {
$event = Webhook::constructEvent(
file_get_contents('php://input'),
$_SERVER['HTTP_WATENGA_SIGNATURE'] ?? null,
getenv('WATENGA_WEBHOOK_SECRET')
);
} catch (SignatureVerificationException $e) {
http_response_code(400);
exit;
}
if ($event['type'] === 'payment.capture.completed') {
// fulfil the order for $event['data']['object']
}
http_response_code(200);Errors
Every failed call raises one error type with the stable code, the HTTP status, the request_id to quote to support, the field at fault (param) and the code's page in the error reference.
use Watenga\WatengaException;
try {
$watenga->refunds->create('txn_...', ['amount' => '5.00', 'reason' => 'customer_request']);
} catch (WatengaException $e) {
echo $e->getErrorCode(), $e->getStatusCode(), $e->getParam(), $e->getRequestId(), $e->getDocUrl();
}Retries and idempotency
Network errors, 429 (after Retry-After) and 5xx answers are retried twice with backoff. Every write carries an Idempotency-Key, kept across those retries, so a retry is answered once and never applied twice; checkout.create uses checkout:<merchant_transaction_id>. See Idempotency.
// Every write gets an Idempotency-Key, kept across the SDK's own retries.
// When you retry a call yourself, pass the same key (last argument):
$watenga->refunds->create('txn_...', ['reason' => 'duplicate'], 'refund-ORD-1001');Pagination
Every list answers one page, { object: "list", data, has_more, next_cursor }; the auto-paginating form reads them all. See Pagination.
$page = $watenga->transactions->list(['status' => 'completed', 'limit' => 50]);
// ['object' => 'list', 'data' => [...], 'has_more' => ..., 'next_cursor' => ...]
foreach ($watenga->transactions->listAll(['status' => 'completed']) as $txn) {
echo $txn['id'], ' ', $txn['amount'], PHP_EOL;
}Resources
The SDK sends Watenga-Version: 2026-10-29; fields use the API's names (snake_case) and money is a decimal string. The same method names in every language.
| Resource | Methods |
|---|---|
checkout | create, getStatus |
checkoutSessions | create, retrieve, expire |
transactions | list, listAll, retrieve, getMobileStatus |
refunds | create |
paymentLinks | create, list, listAll, retrieve, update, deactivate, clone |
invoices | create, list, listAll, retrieve, update, send, remind, void |
quotations | create, list, listAll, retrieve, update, issue, void, convertToInvoice |
plans | create, list, listAll, retrieve, update, delete |
subscriptions | create, list, listAll, retrieve, cancel, pause, resume, upcomingInvoice |
billing | recordUsage, reverseUsage, checkEntitlement |
customers | create, list, listAll, retrieve, update |
paymentMethods | create, list, listAll, retrieve, delete, charge |
account | getBalance, getProfile, getApiKey |
Webhook | constructEvent, generateTestSignature |
Resources come back as arrays; Watenga\Types names their fields (PHPStan/Psalm shapes generated from the OpenAPI document).
Refunds and payouts
refunds scope (Refunds). Payouts to your bank are dashboard-only, with two-step verification: they are not in the API or the SDKs.