Start with shared fields, then the Mexico or Colombia guide. The CFDI, RFC, SAT catalog, and payment-complement examples below describe Mexico; they are not universal country requirements.
Overview
The Payments API provides comprehensive payment management with flexible automation options. Register payments manually, create payment requests, process refunds, and automatically generate compliant invoices.Key Features
- Payment Registration - Record payments with optional invoice automation
- Payment Requests - Create shareable payment links
- Client Search - Find and update existing clients to prevent duplicates
- Payment Splitting - Split payments between master and connect teams (marketplace)
- Refund Processing - Full or partial refund support
- Invoice Automation - Automatic PUE/PPD invoice creation
- PPD Complement Linking - Link payments to existing PPD invoices for automatic complement generation
- Multiple Processors - Support for various payment gateways
- Status Tracking - Real-time payment status updates
- Idempotency - Prevent duplicate payment processing
Endpoints
List Payments
Metadata Filtering:
You can filter payments by any metadata key using dot notation or underscore notation:
Search Payments
Example Request:
Register Payment
- Payment is marked as ‘succeeded’ immediately
- Requires
payment_formfield (Mexican SAT payment form code) - Optional invoice automation
- Client search to prevent duplicate client records
- Used for payments already received
pue_invoice- Create PUE (Pago en Una sola Exhibición) invoice immediatelynone- No automation, register payment only
idempotency_key(string) - Stable key for one business payment. A repeated registration returns400 resource_conflictwith the existing payment ID in the error details; retrieve and reconcile that payment. Keep the same key on retries. See the tested payment recipe.date(number) - Unix timestamp (in milliseconds) for when the payment was received. Must be in the past. Defaults to current time if not provided.exchange_rate(number) - Exchange rate for currency conversion. If not provided, the rate from the payment date (or current date if no date specified) will be fetched automatically from our rates collection.ppd_invoice_id(string) - UUID of an existing PPD invoice to link this payment to. When provided, a payment complement (complemento de pago) will be automatically generated and linked to the PPD invoice. The referenced invoice must havepayment_method='PPD',status='valid'andinvoice_type='I'— a payment complement only ever settles an income CFDI, never an egress one.
01- Cash02- Check03- Electronic transfer04- Credit card05- Electronic money06- Digital money99- To be defined
search parameter enables upsert-like behavior to find existing clients before creating payments, helping to avoid duplicate client records:
on_key(string, required) - Field to search on (e.g., ‘tax_id’, ‘email’, ‘name’)on_value(string, required) - Value to match against the specified fieldupdate(boolean, optional) - If true and a match is found, update the existing client with the provided data. If false, return the existing client without modifications. Default: false
- If a single client matches the search criteria, that client is used for the payment
- If
update: true, the matched client is updated with any new data provided in the request - If multiple clients match the search criteria, a 409 Conflict error is returned to prevent ambiguity
- If no client matches and client data is provided, a new client is created automatically
Request Payment
- Payment is created with ‘requires_payment_method’ status
- Generates shareable payment link
- Supports multiple payment methods
- Optional email notifications
- Used for requesting future payments
- Optionally returns the payer to your site after paying (
success_url)
card- Credit/debit card payments (requires Stripe integration)bank- Mexican bank transfer (SPEI)oxxo- OXXO convenience store payments (requires Stripe integration)stripe-spei- Customer balance payments (requires Stripe integration)
stripe; other supported processors have their own integrations and method lists.
Use the request-payment contract for the current
processor/method combinations. In particular, a payment method being valid in the
shared enum does not make it valid for every processor.
Request Body:
Returning the payer to your site (success_url)
By default the hosted payment page is where the journey ends. For a shop
checkout that is a dead end: the customer pays and is left on a page with no
order reference and no way back, which reads as a failed purchase and leads to
duplicate payments.
Set success_url to your order confirmation page and gigstack sends them back:
400 is returned unless the URL:
- uses
https - carries no credentials (
https://user:pass@host) - contains no whitespace or control characters
- resolves to a fully qualified, publicly reachable host — loopback, private and link-local addresses are rejected
- is at most 2048 characters
success_url on payment reads, and is accepted only on
POST /payments/request. /payments/register records payments that already
settled, where there is no payer to redirect.
Example Request:
Get Payment
Mark Payment as Paid
Cancel Payment
Refund Payment
Do not send
items: it is not an accepted input. The payment must be succeeded.
The cumulative refunds cannot exceed the payment amount. Use a test key and a payment
created in that mode for a test run; substituting a live payment ID is not a test.
The original payment’s automation_type: "none" does not disable refund Journeys or
team automatic refund handling. Test mode can still run published test Journeys and
deliver processor webhooks or team notifications. Before testing, check the account’s
refund settings, webhook destinations and recipients; see the
refund walkthrough.
1. Read the payment and its prior refunds
UseGIGSTACK_BASE_URL and GIGSTACK_API_KEY from the quickstart, and
set PAYMENT_ID to the intended payment. The examples require Bash, curl, and jq.
data.total is in currency units, but the current
data.total_refunded is in minor units (100 minor units per currency unit in this
handler). Each refunds[].total is in currency units. For a payment of 1160 with
total_refunded: 50000, the already-refunded amount is 500 and the remaining amount
is 660. Do not subtract total_refunded directly from total.
2. Record an external refund
Run this only after your bank transfer or other external refund has completed. It records MXN 100 returned against a Mexican-peso example payment; it moves no money:200, a new data.refund.id,
data.refund.total: 100, and data.refund.external_processor_refund: false.
The nested data.payment.amount and data.payment.total_refunded in this response
are both in minor units. An additional staging check exercised this record-only
path with a refund of MXN 116 against a fresh MXN 1,160 payment and verified the
returned refund and cumulative amount by reading the payment again. The MXN 100
variation shown here is source-checked; the refund walkthrough
contains the exercised amount and test evidence.
3. Request a Stripe refund instead
This is an alternative to step 2 for a payment with a linked Stripe payment intent. Sending it after step 2 would create another refund. Choose the amount still owed:true. Do not infer support for other payment processors
from their availability for checkout. A successful response records the refund after
the Stripe call returns; verify the processor’s refund status before promising the
customer that the money has reached their account.
4. Reconcile the result
ReadGET /payments/{id} again and find the returned refund ID in data.refunds.
Compare the increment in total_refunded with the requested amount multiplied by 100.
For an external-processor refund, also reconcile the underlying Stripe payment.
A later Stripe webhook can set partial_refunded or refunded and append a processor
refund entry alongside the API-created entry. Do not count those entries as separate
refunds or sum them to infer money returned; compare the cumulative amount with Stripe.
There is no refund idempotency key in this API. Do not blindly repeat the POST
following a timeout or 500: Stripe may have accepted the request before the local
record was saved. Compare the before/after refund list and the processor record;
if the outcome remains unknown, ask support to reconcile it before sending another
refund. Serialise refund requests for the same payment in your application.
Support Documents
Payment Structure
Payment Status
Payment Processors
- stripe - Stripe payment gateway
- mercado_pago - MercadoPago
- paypal - PayPal
- manual - Manual/bank transfer
Complex Payment Examples
Register Payment with Client Search and Update
This example demonstrates how to search for an existing client by tax ID and update their information if found, or create a new client if not found:- With
update: true, if a client with tax_id “PEGJ800101ABC” exists, their email, phone, and address will be updated - If no client is found, a new client will be created with all the provided information
- The search ensures you don’t create duplicate clients when processing recurring payments
Register Payment with Multiple Items
Register Payment with Withholding Taxes
Payment Request with Custom Invoice Config
Register USD Payment with Exchange Rate
Payment Splitting (Marketplace)
Split payments between a master team (platform) and a connect team (merchant) in marketplace scenarios. This is only available for master teams with marketplace-enabled billing accounts. Important: When usingtransfer_data, the team and livemode fields are automatically extracted from the authentication token. Developers do not need to send these fields in the request body.
master(number, 0-100) - Percentage of the payment for the master teamconnect(string) - Tax ID (RFC) or Team ID of the connect team. If not found, a new team will be createdmaster_to(string: ‘client’ | ‘connect’) - Client assignment for master payment:client: Use the original client from the requestconnect: Create the connect team as a client for the master payment
connect_to(string: ‘client’ | ‘master’) - Client assignment for connect payment:client: Use the original client from the requestmaster: Create the master team as a client for the connect payment
connect_custom_config(object, optional) - Customize items in the connect payment:product_key(string) - SAT product key for connect payment itemsunit_key(string) - SAT unit key for connect payment itemscustom_description(string) - Custom description for connect payment itemscustom_price(number) - Fixed amount for connect payment (overrides percentage calculation)taxes(array) - Custom tax configuration for connect payment items
onboarding_url that can be sent to the merchant to complete their team setup.
Advanced: Split Payment with Custom Configuration
Fixed Commission Fee (Custom Price)
Instead of percentage-based splitting, you can charge a fixed commission fee usingcustom_price. This is useful when you want to charge a flat platform fee regardless of the transaction amount.
- When
custom_priceis set, it overrides the percentage calculation - The connect team gets the exact
custom_priceamount (e.g., $50) - The master team gets the remainder (e.g., 1160 total)
- The
masterpercentage field is ignored when usingcustom_price - Useful for fixed platform fees, minimum commissions, or tiered pricing
Common Scenarios
1. Register Completed Payment (Bank Transfer)
2. Create Payment Link
3. Register Cash Payment
4. Register Payment with PPD Invoice Complement
Register a payment linked to an existing PPD invoice. This automatically generates a payment complement (complemento de pago) CFDI.- The
ppd_invoice_idmust reference a valid PPD income invoice (payment_method='PPD',status='valid',invoice_type='I') - The payment complement is generated automatically by proserver after the payment is created
- You can use
automation_type: "none"since the complement is handled viappd_invoice_id - The PPD invoice’s
paymentsarray is updated to link back to this payment
5. Partial Refund
This example records a refund already completed outside gigstack; it does not return money through Stripe. Read Refund Payment for the alternative processor request, amount units and timeout recovery.6. Marketplace Payment Split (Platform Fee)
7. Marketplace Payment with Fixed Commission
Payment Workflows
Immediate Payment (PUE)
Deferred Payment (PPD)
Payment Request Flow
Best Practices
- Use idempotency keys - Use one stable
idempotency_keyper business payment on creation; reuse it for retry attempts. Refunds do not accept this key. - Use client search - Leverage the
searchparameter to avoid duplicate client records when processing recurring payments. - Set automation_type correctly - Choose the appropriate automation type based on your invoicing workflow (PUE for immediate, PPD for deferred).
- Include all items - Ensure complete and accurate payment information for proper invoice generation.
- Validate clients first - Verify fiscal data (RFC, tax system) before processing payments to avoid stamp failures.
- Use metadata - Track internal references, order IDs, and other business-specific data for reconciliation.
- Handle webhooks - Process payment status updates to keep your systems synchronized.
- Test in staging - Validate all workflows in the staging environment before deploying to production.
Related Resources
- Clients API - Manage payment recipients
- Services API - Configure payment items
- Invoices API - Generated invoices from payments
- Teams API - Configure payment settings
Error Handling
Payment handler errors generally use this envelope; authentication and gateway failures can have a different body. Check the HTTP status before assumingerror.code exists:
At a glance
Payments themselves do not stamp CFDIs synchronously. When a payment carries anautomation_typethat produces an invoice, the stamping happens downstream and asynchronously — a2xxfromPOST /payments/registermeans the payment was recorded, not that a CFDI exists. Subscribe to theinvoice.created/invoice.failedwebhook events for the stamping outcome, and expect the CFDI-specific statuses (412SAT not connected,503PAC unavailable,429credit limit) to surface on the invoice endpoints rather than here.
409 — Multiple clients match search criteria
details lists the ids that matched, so you can resolve the ambiguity without a second query. Not retryable — the same request will conflict again.
Do: pass the exact client id, or search on a more selective key (tax_id over name), or deduplicate the clients.
409 — Concurrent resource creation
Service creation in progress … for an item). gigstack refused to race it rather than create a duplicate.
Do: this one is retryable — wait ~1s and send the same request again; the resource will exist. If you are firing many payments for a new client in parallel, create the client once up front and reference it by id.
400 — Invalid state transition
The lifecycle endpoints reject operations that do not apply to the payment’s current status:
All terminal. Read the payment first (
GET /payments/{id}) and branch on status rather than probing.
400 — Refund exceeds the payment
total in currency units and total_refunded in minor units. Subtract (total_refunded ?? 0) / 100 from total; see Refund Payment.
400 — External processor refund not allowed
external_processor_refund: true on a payment that has no paymentIntent — there is no upstream charge for gigstack to refund. Manually registered payments (cash, transfer) are in this category.
Do: omit external_processor_refund to record the refund in gigstack only, and move the money back yourself.
Failed Client Fiscal Information (400)
When a client’s fiscal information fails validation (invalid RFC or EFOS blacklist), the payment is rejected. Update the client (PUT /v2/clients/{id}) and retry.
For additional assistance, contact support@gigstack.io