The PixGo API is a simple REST — no SDK, no exotic dependency. If you can make a POST over HTTP, you can integrate. This guide covers the whole flow: generate key, create payment, receive webhook, validate signature — with copy-paste code examples.
- Base URL:
https://pixgo.org/api/v1 - Auth: header
X-API-Key: pk_...(generate in My Store) - 3 main endpoints:
POST /payment/create,GET /payment/{id},GET /payment/{id}/status - Webhooks: 4 events (
order.created,payment.completed,payment.expired,payment.refunded) with HMAC-SHA256 - Rate limit: 10,000 req/24h default, 1,000 req/24h on the status endpoint only
- No sandbox: every key is production. Test with small amounts.
Generating your API Key
- Go to the API Keys menu (pixgo.org/api-keys)
- Click Request access and complete the validation shown on screen
- Once access is approved, the key appears on the same page (API Key field)
- Copy the key (format
pk_+ random string) and store securely
- It's one key per user — generating a new one auto-invalidates the previous
- Can't edit/rename — only replace
- Keep in env variable (
.env, secrets manager) — never in versioned code - It's a production key — every call charges/receives for real
- If leaked, generate a new one IMMEDIATELY (old one stops working)
Plug PIX charges into your system with an API and webhooks. See the PixGo PIX API.
Create accountEndpoint 1 — Create payment
Endpoint to generate a QR Code charge. Everything starts here.
POST https://pixgo.org/api/v1/payment/create
Headers:
X-API-Key: pk_your_key_here
Content-Type: application/json
Body (JSON):
{
"amount": 100.00,
"description": "Product X - SKU 123",
"external_id": "order_42",
"receiver_name": "Maria Silva",
"receiver_cpf": "12345678900",
"receiver_email": "[email protected]",
"receiver_phone": "11999998888",
"webhook_url": "https://yoursite.com/webhooks/pixgo"
}Request fields
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Amount in BRL. Min R$10, max per your tier |
description | string | No | Shows on the payer's receipt |
external_id | string | No | Your internal order ID — returns in webhook |
receiver_name | string | No | Customer name — returns in webhook |
receiver_cpf | string | Yes | Payer's CPF or CNPJ, digits only (11 or 14). Without it the charge is rejected with 422 PAYER_DOC_REQUIRED |
receiver_email | string | No | Customer email |
receiver_phone | string | No | Customer phone |
webhook_url | string | No | Specific URL for this payment (overrides global) |
Success response (201)
{
"success": true,
"data": {
"payment_id": "019c8b9149ec7433a5065b834cb45ccc",
"external_id": "order_42",
"amount": 100.00,
"status": "pending",
"qr_code": "00020126360014BR.GOV.BCB.PIX...",
"qr_image_url": "https://pixgo.org/qr/019c8b9149ec...png",
"expires_at": "2026-05-15T14:30:00-03:00",
"created_at": "2026-05-15T14:00:00-03:00"
}
}The qr_code field is the Copy-and-Paste (BR Code, EMV format). qr_image_url is a public URL to show the QR Code image.
payment_id in your DB, linked to your external_id. You'll use it to check status later and to match against webhooks.Error responses
Every error returns success: false, a stable code in error and a human-readable message. Branch on error — the message text may change.
{
"success": false,
"error": "PAYER_DOC_REQUIRED",
"message": "Envie o campo receiver_cpf com o CPF ou CNPJ do pagador (somente números)."
}| HTTP | error | What to do |
|---|---|---|
| 401 | MISSING_API_KEY | The X-API-Key header is missing |
| 401 | INVALID_API_KEY | Key does not exist or is disabled |
| 400 | VALIDATION_ERROR | Required field missing or malformed value |
| 400 | invalid_webhook_url | Use HTTPS with a public domain or IP — localhost, 127.0.0.1 and private IPs are rejected |
| 400 | LIMIT_EXCEEDED | Amount above your tier's limit |
| 422 | PAYER_DOC_REQUIRED | Send receiver_cpf with the payer's CPF or CNPJ. Required at any amount |
| 422 | PAYER_BLOCKED | Payer document failed the anti-fraud check. Ask for a different CPF/CNPJ — retrying will not help |
| 422 | PAYER_COMPLIANCE_BLOCKED | Same, blocked at the compliance layer |
| 429 | PAYER_RATE_LIMIT | Too many charges for the same document in the last hour |
| 429 | RATE_LIMIT_EXCEEDED | Your key's limit was hit — honour Retry-After |
| 503 | PROVIDER_UNAVAILABLE | Banking partner is down. Transient: retry after Retry-After (120s) |
receiver_cpf is now required at any amount. If your integration did not send this field, it will receive 422 PAYER_DOC_REQUIRED until it does. Accepts CPF (11 digits) or CNPJ (14), digits only — punctuation is ignored.Endpoint 2 — Payment details
Returns the full payment object — useful for batch reconciliation or showing detailed status to the customer.
GET https://pixgo.org/api/v1/payment/{id}
Headers:
X-API-Key: pk_your_key_hereResponse includes all create fields + post-payment data (date, fee breakdown, masked payer data). This is the endpoint with standard rate limit (10,000 req/24h).
Endpoint 3 — Payment status
Slim version of the previous one — returns only current status + a few basic fields. Designed for quick polling (you check every N seconds while the customer is on the PIX screen).
GET https://pixgo.org/api/v1/payment/{id}/status
Headers:
X-API-Key: pk_your_key_here
Response:
{
"success": true,
"data": {
"payment_id": "019c8b91...",
"external_id": "order_42",
"amount": 100.00,
"status": "completed",
"customer_name": "Maria Silva",
"customer_cpf": "***.***.***-00",
"customer_phone": "***988",
"created_at": "2026-05-15T14:00:00-03:00",
"updated_at": "2026-05-15T14:25:30-03:00"
}
}/payment/{id}. Use polling with reasonable interval (3-5 seconds, not 100ms). For reliable confirmation, prefer webhook over heavy polling.Webhooks — the recommended path
Webhooks are HTTP notifications PixGo automatically sends to your server when payment status changes. More efficient than constant polling.
Setup
Two places to define webhook URL:
- Global (My Store): single URL that receives ALL account events. Set at pixgo.org/minha-loja → Webhook.
- Per payment: pass
webhook_urlin the body ofPOST /payment/create. Overrides global only for that payment.
Available events
| Event | Fires when |
|---|---|
order.created | Payment was created (right after POST /payment/create) |
payment.completed | PIX was confirmed (status moved to completed) |
payment.expired | QR expired without payment (20min standalone, 30min Charges) |
payment.refunded | Payment was definitively reversed (MED verdict against or voluntary refund) |
Headers PixGo sends
POST /your-endpoint
X-Webhook-Event: payment.completed
X-Webhook-Signature: a3f5...c4d (HMAC-SHA256)
X-Webhook-Timestamp: 1715789430
Content-Type: application/jsonEnriched payload — full structure
Each webhook sends a JSON with up to 5 objects. Here a payment.completed example:
{
"event": "payment.completed",
"payment_id": "019c8b91...",
"external_id": "order_42",
"amount": 100.00,
"completed_at": "2026-05-15T14:25:30-03:00",
"customer": {
"name": "Maria Silva",
"cpf": "12345678900",
"email": "[email protected]",
"phone": "11999998888",
"address": "...",
"custom_field": "campaign_october"
},
"payer": {
"name": "Maria",
"cpf": "***.***.789-00"
},
"product": {
"business_name": "My Store",
"description": "Product X - SKU 123",
"value": 100.00
},
"amounts": {
"gross": 100.00,
"fee_pixgo": 2.00,
"fee_liquid": 0.50,
"fee_total": 2.50,
"net": 97.50,
"currency": "BRL"
}
}What each object means
customer— data the customer filled at checkout (or you passed inPOST /payment/create). Use for CRM, shipping label, receipt.payer— data of who actually paid the PIX (from the bank). Usually same as customer, but can differ (e.g. husband pays from wife's CPF). CPF arrives masked by LGPD.product— store/charge data at creation moment.amounts— full breakdown of fees and net amount landing in your wallet. More details in PixGo fees.
Validating the HMAC signature
Every webhook is signed so you're sure it's PixGo, not a malicious actor faking. Always validate.
Signature logic
- Get the
X-Webhook-Timestampheader and the raw request body - Concatenate:
timestamp + "." + body_raw - Compute HMAC-SHA256 of that string using your Webhook Secret (starts with
whsec_and shows on the API Keys page) as secret - Compare with
X-Webhook-Signature - If matches → authentic. If not → reject (HTTP 401 or 400).
PHP example
<?php
$secret = 'whsec_your_webhook_secret_here';
$body = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$received_sig = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$payload = $timestamp . '.' . $body;
$expected_sig = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected_sig, $received_sig)) {
http_response_code(401);
exit('Invalid signature');
}
// OK, process the event
$data = json_decode($body, true);
if ($data['event'] === 'payment.completed') {
// ... your logic
}
http_response_code(200);
echo 'OK';Python (Flask) example
import hmac, hashlib
from flask import request, abort
SECRET = b'whsec_your_webhook_secret_here'
@app.route('/webhooks/pixgo', methods=['POST'])
def webhook():
body = request.get_data()
timestamp = request.headers.get('X-Webhook-Timestamp', '')
received = request.headers.get('X-Webhook-Signature', '')
payload = f'{timestamp}.{body.decode()}'.encode()
expected = hmac.new(SECRET, payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, received):
abort(401)
data = request.get_json()
if data['event'] == 'payment.completed':
# ... your logic
pass
return 'OK', 200Node.js (Express) example
const crypto = require('crypto');
const express = require('express');
const app = express();
const SECRET = 'whsec_your_webhook_secret_here';
app.post('/webhooks/pixgo',
express.raw({type: 'application/json'}),
(req, res) => {
const body = req.body.toString();
const ts = req.header('X-Webhook-Timestamp') || '';
const received = req.header('X-Webhook-Signature') || '';
const expected = crypto
.createHmac('sha256', SECRET)
.update(`${ts}.${body}`)
.digest('hex');
if (expected !== received) return res.status(401).send('Invalid');
const data = JSON.parse(body);
if (data.event === 'payment.completed') {
// ... your logic
}
res.send('OK');
}
);Rate Limits — what matters
| Endpoint | Limit | Tip |
|---|---|---|
POST /payment/create | 10,000 req/24h | Plenty for normal use |
GET /payment/{id} | 10,000 req/24h | Use for batch reconciliation |
GET /payment/{id}/status | 1,000 req/24h | Limited — prefer webhook |
These are defaults for all accounts. If you need more, open a ticket at pixgo.org/chamados explaining the case — not self-service.
Best practices for robust integration
- Idempotency: webhooks can be delivered more than once (on retry). Use
payment_id+eventas unique key in your DB to skip duplicates. - Respond 2xx quickly: receive webhook, validate signature, enqueue heavy processing (queue/job) and respond 200. Don't block the webhook with long synchronous work (will timeout and PixGo retries).
- Store
external_id: pass your own internal ID inPOST /payment/create. Returns in every webhook — makes order reconciliation easy. - Handle
payment.refundedmandatorily: refunds happen (MED, or you asked). Ignoring the event breaks your accounting. - Don't trust front-end only: when getting confirmation on customer screen via polling, server-side validate with webhook before releasing digital product. Front can be tampered.
- Log everything: save each received webhook + signature + processing status. If something breaks, this is what saves you.
- Set up retry in your webhook handler: PixGo retries on 5xx, but if you respond 200 and then fail in processing, you lost it. Have your own queue with internal retry.
WordPress, WooCommerce, plugins?
PixGo doesn't have an official plugin for any platform yet. Integration is always via REST API. Since the API is simple, any dev can plug in in a few hours:
- WordPress / WooCommerce: implementation via custom plugin — you write a payment gateway calling our API on
process_paymentand receive webhook at your own URL. - Shopify: use Shopify Apps with own webhook — same structure.
- Magento, OpenCart, PrestaShop: custom module following each platform's pattern.
- Custom SaaS: direct integration into your backend, even simpler.
Recurrence (subscription)
The current API has no native recurrence endpoint (like "create automatic monthly subscription"). Current path is:
- In your system, you keep the list of active customers and billing cycle
- At each renewal date, your routine calls
POST /payment/createwith customer's amount - Customer pays (or not) each charge individually — PixGo doesn't debit the customer automatically
- If they pay, you process
payment.completedwebhook and renew the cycle - If not, you decide the rule (notify, suspend, grace period)
PixGo also has a PIX recurring billing module that does this for those who prefer the ready interface without coding — worth checking before implementing from scratch.
Developer FAQ
Sandbox / test environment?
No. Every key is production. To test, use small values (R$10-R$15) and pay from your own CPF — then swap inside PixGo Wallet if you want to recover.
Can I filter payments by external_id?
Yes. Call GET /api/v1/payments?external_id=YOUR_ID with your X-API-Key: the response lists the payments with that external_id (up to 50 characters) and comes back empty if there are none. Still, store the payment_id at create time, since it identifies each charge in the webhooks.
Does the webhook have automatic retry?
Yes. If your URL returns 5xx or times out, PixGo retries with exponential backoff for some hours. If still fails, eventually gives up. So respond 2xx fast (even if processing async).
Can I use the same key on multiple servers?
Yes. Key has no origin limit. But mind the rate limit (cumulative across all servers using the same key).
Is CORS enabled? Can I call the API from front-end?
DO NOT call the API from front-end. Your key must never end up in browser JavaScript. Always use your backend as intermediary. CORS isn't the issue — key security is.
Can I test webhook locally?
Yes, using tunnels (ngrok, localtunnel, Cloudflare Tunnel). Create a tunnel to your localhost, register that URL as webhook_url of the test payment, and PixGo sends directly to your local server.
What's the webhook timeout?
You have ~10 seconds to respond 2xx. Slower than that, considered failure and retried.
Is there an SDK in any language?
No. The API is plain REST — any HTTP client works. No official SDK in Node/Python/PHP/Go. Community may have wrappers — not official and may be outdated.
Does external_id have to be unique?
Not validated by PixGo. You can even repeat, but that'll hurt your reconciliation. Recommended: use UUIDs or auto-increment IDs.
Can I pass JSON in custom_field?
The field is a string. You can pass serialized JSON ('{"tag":"oct2026"}') — your app parses on receive. No hard size limit, but keep short (few KB).
Practical summary
- Simple REST API — Base
https://pixgo.org/api/v1+ headerX-API-Key - 3 main endpoints: create, details, status
- Webhooks with 4 events + HMAC-SHA256 (ALWAYS validate)
- Rate limit: 10k/24h general, 1k/24h on /status only
- No SDK / no sandbox — every key is production
- No official plugin — custom integration on WP, Shopify, etc
- Recurrence: external routine calling /create on renewal date
- Live docs: pixgo.org/api/v1/docs
Related reading: Fees and amounts breakdown · Per-tier limits · Handling MED in your integration