Open the Merchant workspace, enter the business identity and webhook URL, then submit for review.
PAYTAPPS developer platform
Create hosted checkout sessions, reconcile payment status, issue refunds, and receive signed events with server-side integrations built for retry safety.
X-PayOffice-Signature-Version: v2
Idempotency-Key: order_1048_attempt_1
{
"amount": "49.99",
"currency": "USD",
"merchant_reference": "ORDER-1048",
"customer": {
"email": "buyer@example.com"
}
}
From account to first payment
Use test mode until the entire redirect, webhook, and reconciliation path succeeds.
An administrator activates test access. Live mode additionally requires approved owner KYC.
Create a separate expiring key for each website, app, or point-of-sale integration.
Send an idempotent signed request and redirect the customer to the returned checkout URL.
Use the completed webhook or retrieve the payment before marking the local order paid.
Never sign requests in browser JavaScript, mobile bundles, page source, or public WordPress settings output.
Canonical request authentication
Version 2 binds the timestamp, HTTP method, exact request target, and raw body digest into one HMAC.
Required headers
X-PayOffice-Key- The scoped public key shown in the merchant workspace.
X-PayOffice-Timestamp- Current Unix timestamp. Requests outside the five-minute tolerance are rejected.
X-PayOffice-Signature-Version- Send
v2. Version 1 remains available only for legacy compatibility. X-PayOffice-Signature- Lowercase HMAC-SHA256 hex digest of the canonical value.
Idempotency-Key- Required by the integration guides for checkout and refund writes.
{unix_timestamp}\n{UPPERCASE_METHOD}\n{request_path_with_query}\n{sha256_hex_of_raw_body}
$body = json_encode($payload, JSON_UNESCAPED_SLASHES);
$timestamp = (string) time();
$target = '/api/v1/checkout';
$canonical = implode("\n", [
$timestamp,
'POST',
$target,
hash('sha256', $body),
]);
$signature = hash_hmac('sha256', $canonical, $secretKey);
Choose an integration
Use the packaged clients or implement the same OpenAPI and signing contract from any server framework.
Laravel client package
The download contains a service provider, configuration file, typed client methods, canonical signing, request retries, and webhook verification.
- Extract the package into
packages/payoffice/laravel-client. - Add a Composer path repository and require
payoffice/laravel-client. - Publish config and add the API URL, public key, secret, and webhook secret to
.env. - Create checkout server-side, then redirect to
checkout_url.
use PayOffice\Laravel\PayOfficeClient;
$checkout = app(PayOfficeClient::class)->createCheckout([
'amount' => number_format($order->total, 2, '.', ''),
'currency' => $order->currency,
'merchant_reference' => $order->number,
'client_reference_id' => 'order_'.$order->id,
'description' => 'Order '.$order->number,
'customer' => [
'name' => $order->customer_name,
'email' => $order->customer_email,
],
'return_url' => route('orders.paid', $order),
'cancel_url' => route('orders.show', $order),
], 'order_'.$order->id);
return redirect()->away($checkout['checkout_url']);
WooCommerce hosted gateway
The plugin supports classic and block checkout, creates a PayOffice session per WooCommerce order, redirects offsite, verifies signed webhooks, and reconciles the order on return.
- Upload the ZIP in WordPress under Plugins, then activate it.
- Open WooCommerce → Settings → Payments → PayOffice.
- Enter the API URL, scoped public key, secret key, and webhook secret.
- Copy the displayed WordPress webhook URL into the merchant workspace.
https://your-store.com/wp-json/payoffice/v1/webhookFramework-free PHP client
The standalone download provides the full merchant API surface, canonical request signing, strict transport checks, and raw-body webhook verification for any PHP 8.1+ application.
- Encode the body once and send those exact bytes.
- Sign the full request target, including any query string.
- Reuse an idempotency key only for the same logical operation.
- Store checkout tokens against your own order identifier.
$client = new PayOfficeClient(
'https://paytapps.com/api/v1',
getenv('PAYOFFICE_PUBLIC_KEY'),
getenv('PAYOFFICE_SECRET_KEY'),
);
$checkout = $client->createCheckout([
'amount' => '49.99',
'currency' => 'USD',
'merchant_reference' => 'ORDER-1048',
'return_url' => 'https://shop.example/orders/1048',
], 'order_1048');
header('Location: '.$checkout['checkout_url']);
Node.js server integration
Use the built-in crypto and fetch APIs. Keep this code in the server process, API route, worker, or serverless function.
- Use
JSON.stringifyonce for signing and sending. - Build the target from the URL pathname and search.
- Check
response.okand log the returned request ID.
import { createHash, createHmac } from 'node:crypto';
const url = new URL('https://paytapps.com/api/v1/checkout');
const body = JSON.stringify({
amount: '49.99',
currency: 'USD',
merchant_reference: 'ORDER-1048'
});
const timestamp = Math.floor(Date.now() / 1000).toString();
const target = url.pathname + url.search;
const bodyHash = createHash('sha256').update(body).digest('hex');
const canonical = [timestamp, 'POST', target, bodyHash].join('\n');
const signature = createHmac('sha256', process.env.PAYOFFICE_SECRET)
.update(canonical).digest('hex');
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-PayOffice-Key': process.env.PAYOFFICE_KEY,
'X-PayOffice-Timestamp': timestamp,
'X-PayOffice-Signature-Version': 'v2',
'X-PayOffice-Signature': signature,
'Idempotency-Key': 'order_1048'
},
body
});
Merchant API reference
Base URL https://paytapps.com/api/v1. Every response includes X-PayOffice-Request-ID.
/merchant
Read merchant status, environment, webhook configuration, and account health.
merchant:read/checkout
Create an idempotent hosted checkout with customer and line-item context.
checkout:create/payments
List and filter merchant payments with pagination.
payments:read/payments/{checkout_token}
Read status, receipt, refund history, and order details.
payments:read/payments/{checkout_token}/cancel
Cancel a pending checkout session.
checkout:cancel/payments/{checkout_token}/refund
Create a full or partial idempotent refund.
refunds:create/refunds/{refund_id}
Retrieve a refund and its wallet transaction reference.
refunds:read/webhooks
Inspect webhook payloads, attempts, responses, and delivery state.
webhooks:read/webhooks/test
Queue a signed test event to the configured endpoint.
webhooks:test/webhooks/{event_id}/retry
Retry a failed, queued, or skipped webhook event.
webhooks:retrySigned webhook delivery
Respond with any 2xx status quickly, deduplicate by webhook ID, and move slow business logic to a queue.
Available events
merchant.payment.completedCheckout payment completedmerchant.payment.cancelledCheckout payment cancelledmerchant.payment.expiredCheckout payment expiredmerchant.payment.refundedCheckout payment refundedmerchant.payment_link.completedPayment link completedX-PayOffice-Webhook-ID
Event typeX-PayOffice-Webhook-Event
AttemptX-PayOffice-Webhook-Attempt
SignatureX-PayOffice-Webhook-Signature
Financial settlement fields: completed payment payloads separate payer_fee, merchant-owned processing_fee, and wallet net_amount. Use the net amount for merchant reconciliation.
$body = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_PAYOFFICE_WEBHOOK_SIGNATURE'] ?? '';
if (! preg_match('/^t=(\d+),v1=([a-f0-9]{64})$/', $header, $match)) {
http_response_code(400);
exit;
}
[$header, $timestamp, $signature] = $match;
if (abs(time() - (int) $timestamp) > 300) {
http_response_code(400);
exit;
}
$expected = hash_hmac(
'sha256',
$timestamp.'.'.$body,
getenv('PAYOFFICE_WEBHOOK_SECRET')
);
if (! hash_equals($expected, $signature)) {
http_response_code(401);
exit;
}
http_response_code(204);
Errors and recovery
Use the HTTP status, response message, field errors, and request ID together when investigating an integration.
Read the returned object. A repeated idempotent write can return 200 with idempotent_replay: true.
The resource does not exist or belongs to a different merchant.
An idempotency key was reused with different data, or a delivered webhook was retried.
Authentication, scope, account state, or field validation failed. Inspect the errors object.
Back off with jitter and retry. Do not change the idempotency key for the same write.
Retry idempotently and keep the request ID for support and API request-log correlation.
Include the request ID, endpoint, timestamp, and merchant public-key prefix. Never send a secret key.
