Registrá una compra y leé el estado resultante
Registrá una `purchase` con idempotencia, interpretá su resultado y verificá el estado posterior del `customer`.
Antes de empezar
- Necesitás un
customer.idya resuelto y elprogram_idcorrespondiente. POST /api/purchasesrequierepurchases.write.GET /api/customers/{id}/staterequierecustomers.read.- Generá una
Idempotency-Keyestable por cada intento lógico de compra.
1. Registrá la compra
El body público de POST /api/purchases usa exactamente customer_id, program_id, total_amount, currency_code y external_reference. Enviá además Idempotency-Key: no puede quedar vacía después de trim y su longitud máxima es 128 caracteres.
2. Interpretá la respuesta
Una respuesta HTTP 200 puede incluir transactionId, pointsEarned, balance, previousTier, currentTier, tierChanged, idempotentReplay y externalReference. transactionId identifica la operación resultante; puntos, balance y tier describen el efecto de loyalty expuesto por la compra.
3. Reintentá sin duplicar efectos
- Misma
Idempotency-Key+ mismo payload → HTTP 200, misma transacción yidempotentReplay = true. - Misma key + payload diferente → HTTP 409
idempotency_conflict. idempotency_in_progress→ repetí únicamente el mismo request con la misma key.purchase_reconciliation_required→ preservá request y key, y reconciliá antes de iniciar otra compra.
4. Leé el estado resultante
Llamá a GET /api/customers/{id}/state. La lectura expone customer.id, programId, points, currentTier, active y recentOperation; cuando existe, recentOperation.transactionId permite correlacionar la operación reciente.
Compra, transacción y estado son conceptos distintos
purchasees el evento comercial que tu integración envía a Qualth.transactionIdidentifica la operación persistida que devuelve el contrato de purchase.- El estado del
customeres la lectura posterior que tu integración puede usar para verificar el resultado observable.
Ejemplos probados
Los requests y replays de esta guía se renderizan desde ejemplos validados contra el contrato pinneado de Integration API.
recordPurchasepurchase-record
- HTTP esperado
- 200
- Última validación
- 2026-08-11
- Evidencia
- deterministic-ci+controlled-smoke
The public purchase request uses exactly customer_id, program_id, total_amount, currency_code and external_reference plus Idempotency-Key.
cURL
curl --request POST \
--url 'https://api.qualth.com/api/purchases' \
--header 'X-API-Key: {{api_key}}' \
--header 'X-Tenant-Subdomain: {{tenant_subdomain}}' \
--header 'Idempotency-Key: {{new_idempotency_key}}' \
--header 'Content-Type: application/json' \
--data '{
"customer_id": {{new_customer_id}},
"program_id": {{program_id}},
"total_amount": {{purchase_amount}},
"currency_code": "ARS",
"external_reference": "{{new_external_reference}}"
}'JavaScript
const response = await fetch("https://api.qualth.com/api/purchases", {
method: "POST",
headers: {
"X-API-Key": "{{api_key}}",
"X-Tenant-Subdomain": "{{tenant_subdomain}}",
"Idempotency-Key": "{{new_idempotency_key}}",
"Content-Type": "application/json"
},
body: JSON.stringify({
"customer_id": Number("{{new_customer_id}}"),
"program_id": Number("{{program_id}}"),
"total_amount": Number("{{purchase_amount}}"),
"currency_code": "ARS",
"external_reference": "{{new_external_reference}}"
}),
})
const data = await response.json()recordPurchasepurchase-identical-replay
- HTTP esperado
- 200
- Última validación
- 2026-08-11
- Evidencia
- deterministic-ci+controlled-smoke
Identical replay returns the same transaction and must not duplicate points, balance or transaction effects.
cURL
curl --request POST \
--url 'https://api.qualth.com/api/purchases' \
--header 'X-API-Key: {{api_key}}' \
--header 'X-Tenant-Subdomain: {{tenant_subdomain}}' \
--header 'Idempotency-Key: {{new_idempotency_key}}' \
--header 'Content-Type: application/json' \
--data '{
"customer_id": {{new_customer_id}},
"program_id": {{program_id}},
"total_amount": {{purchase_amount}},
"currency_code": "ARS",
"external_reference": "{{new_external_reference}}"
}'JavaScript
const response = await fetch("https://api.qualth.com/api/purchases", {
method: "POST",
headers: {
"X-API-Key": "{{api_key}}",
"X-Tenant-Subdomain": "{{tenant_subdomain}}",
"Idempotency-Key": "{{new_idempotency_key}}",
"Content-Type": "application/json"
},
body: JSON.stringify({
"customer_id": Number("{{new_customer_id}}"),
"program_id": Number("{{program_id}}"),
"total_amount": Number("{{purchase_amount}}"),
"currency_code": "ARS",
"external_reference": "{{new_external_reference}}"
}),
})
const data = await response.json()recordPurchasepurchase-idempotency-conflict
- HTTP esperado
- 409
- Última validación
- 2026-08-11
- Evidencia
- deterministic-ci+controlled-smoke
Reusing an Idempotency-Key with a different payload is an explicit 409 idempotency_conflict.
cURL
curl --request POST \
--url 'https://api.qualth.com/api/purchases' \
--header 'X-API-Key: {{api_key}}' \
--header 'X-Tenant-Subdomain: {{tenant_subdomain}}' \
--header 'Idempotency-Key: {{new_idempotency_key}}' \
--header 'Content-Type: application/json' \
--data '{
"customer_id": {{new_customer_id}},
"program_id": {{program_id}},
"total_amount": 1600,
"currency_code": "ARS",
"external_reference": "{{new_external_reference}}"
}'JavaScript
const response = await fetch("https://api.qualth.com/api/purchases", {
method: "POST",
headers: {
"X-API-Key": "{{api_key}}",
"X-Tenant-Subdomain": "{{tenant_subdomain}}",
"Idempotency-Key": "{{new_idempotency_key}}",
"Content-Type": "application/json"
},
body: JSON.stringify({
"customer_id": Number("{{new_customer_id}}"),
"program_id": Number("{{program_id}}"),
"total_amount": 1600,
"currency_code": "ARS",
"external_reference": "{{new_external_reference}}"
}),
})
const data = await response.json()getCustomerStatecustomer-state-after-purchase
- HTTP esperado
- 200
- Última validación
- 2026-08-11
- Evidencia
- deterministic-ci+controlled-smoke
State is sanitized, contains no customer profile email and reflects the most recent purchase transaction.
cURL
curl --request GET \
--url 'https://api.qualth.com/api/customers/{{new_customer_id}}/state' \
--header 'X-API-Key: {{api_key}}' \
--header 'X-Tenant-Subdomain: {{tenant_subdomain}}'JavaScript
const response = await fetch("https://api.qualth.com/api/customers/{{new_customer_id}}/state", {
method: "GET",
headers: {
"X-API-Key": "{{api_key}}",
"X-Tenant-Subdomain": "{{tenant_subdomain}}"
}
})
const data = await response.json()