Custom Website Integration
Any website — PHP, Node.js, Python, Ruby, .NET, static + serverless — can accept Bitcoin (and multi-coin) payments through the Jamesinvoicing REST API. Create an invoice from your server, redirect the customer to our hosted payment page, and receive a signed webhook when payment confirms.
How it works
- Sign up at jamesinvoicing.com/auth and add a verified xPub or approved receiving address.
- Generate an API key in Dashboard → API Keys and store the key + webhook secret as environment variables on your server.
- From your checkout, call
POST /merchant/generate-addressto create an invoice. - Redirect the customer to the returned
pay_url. - Receive a signed webhook at your endpoint when the invoice becomes
Paid.
Base URL: https://jamesinvoicing.com/api/public/v1
1. Create an invoice & redirect the customer
The response includes payment_id, pay_url, all offered coin addresses, and expires_at. Send the customer to pay_url.
curl -X POST https://jamesinvoicing.com/api/public/v1/merchant/generate-address \
-H "Authorization: Bearer pch_live_xxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"order_number": "ORD-1001",
"customer_email": "customer@example.com",
"customer_name": "Jane Doe",
"description": "Pro plan — 1 month",
"amount_usd": 49.00,
"amount_btc": 0.00072,
"btc_rate": 68000,
"expires_in_minutes": 60
}'2. Check invoice status (optional)
Webhooks are the recommended source of truth, but you can poll the invoice status by payment_id at any time.
curl https://jamesinvoicing.com/api/public/v1/merchant/invoice-status/PAYMENT_ID \
-H "Authorization: Bearer pch_live_xxxxxxxxxxxxxxxxxxxxxxxxx"3. Receive & verify webhooks
Every webhook is signed with HMAC-SHA256 over the raw request body using your webhook_secret. The signature is sent in the X-PCH-Signature header. Reject any request whose signature does not match.
<?php
// webhook.php — set this URL in Jamesinvoicing → Settings → Webhooks
$secret = getenv('PCH_WEBHOOK_SECRET');
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_PCH_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $raw, $secret);
if (!hash_equals($expected, $sig)) {
http_response_code(401);
exit('invalid signature');
}
$event = json_decode($raw, true);
// $event['type'] => 'invoice.paid' | 'invoice.confirming' | 'invoice.expired' | ...
// $event['data'] => full invoice object
if ($event['type'] === 'invoice.paid') {
markOrderPaid($event['data']['order_number']);
}
http_response_code(200);
echo 'ok';Event types & recommended order status
invoice.confirming— payment detected on-chain. Flip your order to Processing and start fulfilment.invoice.paid— required confirmations reached. Flip your order to Completed.invoice.expired— customer did not pay beforeexpires_at. Mark as Cancelled / Failed.invoice.cancelled— invoice cancelled by the merchant. Mark as Cancelled.
Same behaviour as our WordPress plugin: detected → Processing, confirmed → Completed. Never mark an order paid on invoice.confirming alone if you need on-chain finality — wait for invoice.paid.
Security checklist
- Store
pch_live_…keys andPCH_WEBHOOK_SECRETas environment variables — never in client code or git. - Always verify
X-PCH-Signaturebefore trusting a webhook payload. - Use
hash_equals/timingSafeEqual/hmac.compare_digest— not==. - Treat
order_numberas the join key back to your own order table. - Serve your webhook over HTTPS only.
Full endpoint reference lives in the API reference.