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.

TL;DR
  • 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

  1. Go to the API Keys menu (pixgo.org/api-keys)
  2. Click Request access and complete the validation shown on screen
  3. Once access is approved, the key appears on the same page (API Key field)
  4. Copy the key (format pk_ + random string) and store securely
Key handling:
  • 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 account

Endpoint 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
amountnumberYesAmount in BRL. Min R$10, max per your tier
descriptionstringNoShows on the payer's receipt
external_idstringNoYour internal order ID — returns in webhook
receiver_namestringNoCustomer name — returns in webhook
receiver_cpfstringYesPayer's CPF or CNPJ, digits only (11 or 14). Without it the charge is rejected with 422 PAYER_DOC_REQUIRED
receiver_emailstringNoCustomer email
receiver_phonestringNoCustomer phone
webhook_urlstringNoSpecific 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.

Important: store the 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
401MISSING_API_KEYThe X-API-Key header is missing
401INVALID_API_KEYKey does not exist or is disabled
400VALIDATION_ERRORRequired field missing or malformed value
400invalid_webhook_urlUse HTTPS with a public domain or IP — localhost, 127.0.0.1 and private IPs are rejected
400LIMIT_EXCEEDEDAmount above your tier's limit
422PAYER_DOC_REQUIREDSend receiver_cpf with the payer's CPF or CNPJ. Required at any amount
422PAYER_BLOCKEDPayer document failed the anti-fraud check. Ask for a different CPF/CNPJ — retrying will not help
422PAYER_COMPLIANCE_BLOCKEDSame, blocked at the compliance layer
429PAYER_RATE_LIMITToo many charges for the same document in the last hour
429RATE_LIMIT_EXCEEDEDYour key's limit was hit — honour Retry-After
503PROVIDER_UNAVAILABLEBanking partner is down. Transient: retry after Retry-After (120s)
Breaking change: 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_here

Response 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"
  }
}
Heads up — lower rate limit: this endpoint is capped at 1,000 req/24h per key. 10× tighter than /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.

Webhook flow: PixGo server sends HMAC-signed notification to your server
PixGo server sends event → signed with HMAC-SHA256 → your server receives, validates and processes.

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_url in the body of POST /payment/create. Overrides global only for that payment.

Available events

Event Fires when
order.createdPayment was created (right after POST /payment/create)
payment.completedPIX was confirmed (status moved to completed)
payment.expiredQR expired without payment (20min standalone, 30min Charges)
payment.refundedPayment 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/json

Enriched 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 in POST /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

  1. Get the X-Webhook-Timestamp header and the raw request body
  2. Concatenate: timestamp + "." + body_raw
  3. Compute HMAC-SHA256 of that string using your Webhook Secret (starts with whsec_ and shows on the API Keys page) as secret
  4. Compare with X-Webhook-Signature
  5. 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', 200

Node.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/create10,000 req/24hPlenty for normal use
GET /payment/{id}10,000 req/24hUse for batch reconciliation
GET /payment/{id}/status1,000 req/24hLimited — 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 practice: never rely on polling as the only path. Set up webhook + status endpoint as fallback. Webhook handles 99% of cases; status just to confirm when the user refreshes the screen.

Best practices for robust integration

  • Idempotency: webhooks can be delivered more than once (on retry). Use payment_id + event as 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 in POST /payment/create. Returns in every webhook — makes order reconciliation easy.
  • Handle payment.refunded mandatorily: 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_payment and 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:

  1. In your system, you keep the list of active customers and billing cycle
  2. At each renewal date, your routine calls POST /payment/create with customer's amount
  3. Customer pays (or not) each charge individually — PixGo doesn't debit the customer automatically
  4. If they pay, you process payment.completed webhook and renew the cycle
  5. 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 + header X-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