Skip to content

PIX Cash-Out Copy and Paste

Performs a PIX transfer using the EMV code (copy and paste) extracted from a PIX QR Code.

Endpoint

POST /api/external/pix/cash-out

Headers

HeaderTypeRequiredDescription
AuthorizationStringYesApiKey {client_id}:{client_secret}
Content-TypeStringYesapplication/json
hmacStringYesHMAC-SHA512 signature of the body (hex)
Idempotency-KeyStringNoUnique key to prevent duplicate processing (max 256 chars). Behavior identical to what is documented in PIX Cash-Out by Key

Required permission

The API Key must have the transfer:write permission to send PIX. Without it, the request returns 403 Forbidden. See how to configure permissions.

Request Body

FieldTypeRequiredDescription
amountIntegerYesAmount in centavos. R$ 30.00 = 3000. On dynamic QR, this value is overridden by the lookup to the QR URL — see below
emvStringYesEMV copy-and-paste code from the PIX QR Code. The alias codigo_copia_cola is also accepted but emv is canonical — prefer it
descriptionStringNoTransfer description (max 140 characters)
external_idStringNoYour system identifier for tracking. Max 128 chars after trim. Only a-zA-Z0-9._:- characters. Returned in responses and webhooks. Invalid values are silently discarded (the transaction proceeds with external_id: null). See PIX Cash-Out by Key for details.
end_to_end_idStringNoEnd-to-End ID in BACEN format. Recommended to omit — the backend generates a deterministic one. Only send in coordinated reprocessing scenarios

Copy-and-Paste code

The emv field accepts the full EMV code copied from a PIX QR Code. The API automatically extracts the PIX key, recipient data, and the original charge amount from the EMV payload. The backend validates CRC-16/CCITT-FFFF and the TLV structure before proceeding.

Static QR vs Dynamic QR

The API treats the two types differently:

Static QR (PoIM=11, no URL):

  • The PIX key and metadata come from the EMV payload itself
  • The amount sent in the request is used directly as the transfer value
  • No external lookup — immediate settlement after DICT validation

Dynamic QR (PoIM=12, with URL in tag 26/25):

  • The backend calls Provider.consult_qrcode on the URL embedded in the EMV payload and receives a JWS (BACEN Manual 2.9.0)
  • The authoritative value comes from the JWS response (valor.original) and overrides the amount sent by the client
  • If the URL lookup fails (timeout, invalid JWS, QR expired at issuer), returns HTTP 422 with detail: "QR Code dinamico nao pode ser resolvido"
  • Use the overridden value as the source of truth — net_amount in the response reflects the actual charged value

Do not rely on the value sent in dynamic QR

On dynamic QR, always check the POST response to get the effective amount. The backend ignores any divergence between the value sent and the value returned by the QR issuer — this is a BACEN requirement.

Monetary values

Request values are in centavos (R$ 1.00 = 100). Response values are in base units (R$ 1.00 = 10000). To convert the response to BRL, divide by 10,000. Never use floating point.

Example

bash
curl -X POST https://api.owem.com.br/api/external/pix/cash-out \
  -H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -H "hmac: $HMAC" \
  -d '{
    "amount": 3000,
    "emv": "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d5204000053039865802BR5913NOME RECEBEDOR6008BRASILIA62070503***6304ABCD",
    "description": "Pagamento via QR Code",
    "external_id": "invoice-4521"
  }'

Success Response -- 200 / 202

json
{
  "worked": true,
  "final": false,
  "transaction_id": "PIXOUT20260309a1b2c3d4e5f6",
  "end_to_end_id": "E37839059202603091530abcdef01",
  "external_id": "invoice-4521",
  "amount": 300000,
  "fee_amount": 350,
  "net_amount": 300350,
  "status": "accepted",
  "detail": "PIX enviado para processamento"
}

HTTP 200 vs 202

  • HTTP 200: Transaction already settled (final: true, status: "settled").
  • HTTP 202: Transaction accepted for processing (final: false). status can be "accepted" (normal flow), "queued" (DICT rate-limit — automatic retry every 3s for up to 120min, session 155) or "pending_approval" (awaiting approval). Track the status via polling or webhook.
FieldTypeDescription
workedBooleantrue indicates the request was accepted
finalBooleantrue when the transaction reached a terminal state (settled or rejected). false when still processing
transaction_idStringUnique transaction identifier
end_to_end_idStringEnd-to-End identifier in SPI/BACEN
external_idStringYour identifier, returned as sent. null if not provided
amountIntegerTransfer amount in base units (÷ 10,000 for BRL). 300000 = R$ 30.00. On dynamic QR, reflects the authoritative value returned by the JWS lookup — may diverge from the amount sent in the request
fee_amountIntegerFee charged in base units (÷ 10,000 for BRL)
net_amountIntegerGross amount debited from the paying account (amount + fee_amount) — this is not what the recipient receives. See the note in PIX Cash-Out by Key on the semantic asymmetry between cash-out and cash-in
statusStringOne of: accepted, settled, queued, pending_approval. See details in PIX Cash-Out by Key
detailStringDescriptive message

Rejection Codes

Cash-out-by-EMV rejections follow exactly the same pattern as PIX Cash-Out by Key — two body formats (Format A {status: "failed", errors: [...]} and Format B {errors: {bad_request: "..."}}), three error classes (validation / integration / queued rate-limit), and the same UPPERCASE × lowercase vocabularies. The items below highlight only the codes specific to the EMV path.

EMV-specific validation errors (HTTP 400 / 422)

HTTPFormatField with codeMeaning
400Berrors.bad_request: "invalid emv payload"Malformed EMV payload (invalid CRC-16, incomplete TLV, missing mandatory tag) — detected by the parser before the Orchestrator
400Berrors.bad_request: "invalid or missing amount"amount missing, zero, negative, or non-integer (static QR)
422Aerrors[0].code: "same_institution_transfer"QR generated by an Owem account — intra-institutional PIX not supported (use TEF). Note: HTTP 422 (not 400), with shape {status: "failed", errors: [{code: "same_institution_transfer", params: []}]}
422detail: "QR Code dinamico nao pode ser resolvido"Failure in the dynamic QR URL lookup (timeout, invalid JWS, QR expired at issuer) — this path is handled by the controller before the Orchestrator, returns legacy shape {errors: {unprocessable_entity: "..."}}
422Aerrors[0].code: "insufficient_balance"Available balance less than amount + fee_amount

Other errors

For the codes dict_key_not_found, dict_key_blocked, dict_rate_limited (synchronous), dict_bucket_exhausted (synchronous), dict_lookup_failed, provider_rejected, provider_schema_error, provider_unknown_errorall return HTTP 400 in Format A (not 429 or 503). Details in PIX Cash-Out by Key — Integration errors.

Rate-limit with automatic retry (HTTP 202 queued)

When the per-merchant ClientLimiter (default DICT_CLIENT_MAX_PER_MIN=120) or the global DictBucket.Guard (refill DICT_BUCKET_REFILL_RATE=18/min, capacity 250) detects that the limit has been reached before reaching OnZ, the cash-out is enqueued in Fluxiq.Workers.PixOutRetryWorker and returns HTTP 202 with status: "queued". The pix_out_retry_queue_enabled flag is ON in PRD since session 155. Full details in PIX Cash-Out by Key — Rate-limit with automatic retry.

Asynchronous BACEN rejections

After initial acceptance (HTTP 202), the SPI/BACEN may reject via PACS.002 RJCT. This rejection:

  • Does not change the original HTTP response (already sent as 202)
  • Appears via GET /transactions/:id with status: "failed" + reason_code (AC03, AB03, ED05 etc.) + reason_description in English
  • Fires webhook pix.payout.rejected with the same structured fields

See the full list of BACEN codes in Query Cash-Out by ID.

Next Steps

After creating the transfer, track the status via:

Or receive confirmation automatically via Webhook.

Owem Pay Instituição de Pagamento — ISPB 37839059