Reportar canje de premio
Ingesta server-to-server de canjes de premios de mini-juegos in-app.
/api/v1/inapp-games/redemptionsRegistra que un premio de mini-juego se canjeo en tu plataforma, con el monto de la venta y el descuento invertido.
Endpoint server-to-server: lo llama tu backend (no el SDK cliente) cuando el premio que un cliente gano en la ruleta se hace efectivo en tu plataforma — una compra con el descuento aplicado, un cupon cobrado, etc. Con estos datos el portal calcula el ROI de la campana (ventas atribuidas, descuento invertido, ticket promedio) en el detalle del mini-juego.
Usa la misma autenticacion que el resto de la API REST (Authorization: Bearer <api_key> + X-Org-Id), con una API key de escritura de tu organizacion.
De donde salen notificationId y awardId
Ambos identificadores te llegan por los canales server-side existentes del mini-juego — no hay que construirlos a mano:
- Webhook
inapp_game.won(recomendado): el payload traenotificationIdyawardIddel premio recien ganado. Ver Webhooks > Eventos. - Resultado del SDK (
onGameResult/ eventoresult:): el mismoawardIdviaja en la respuesta del endpoint spin que el SDK entrega al app host.
awardId ("{spinPeriod}_{clientId}") identifica de forma unica el premio de ese cliente en ese periodo de giro — es la misma clave de idempotencia que ya usas para acreditar el premio.
Si tu backend no guarda el awardId (solo tu id de usuario y tu id de premio), salta a la seccion Identificacion alternativa (sin awardId) mas abajo.
Request body
| Parametro | Tipo | Descripcion |
|---|---|---|
| notificationId* | string | Id de la notificacion de mini-juego (viene en el webhook inapp_game.won). Letras, numeros, _ : -, maximo 200 caracteres |
| awardId* | string | Id del premio a canjear (mismo awardId del webhook / resultado del SDK). Letras, numeros, _ : -, maximo 200 caracteres. Alternativa: omitelo y envia clientExternalId + segmentExternalId |
| saleTotal* | int | Total de la venta asociada al canje, entero >= 0 en unidades menores de la moneda (CLP sin decimales: pesos; USD: centavos). Maximo 2^53-1 |
| discountAmount* | int | Descuento efectivamente aplicado, entero >= 0 en unidades menores de la moneda. Maximo 2^53-1 |
| currency* | string | Codigo de moneda de 3 letras mayusculas (CLP, USD, EUR...). Otro formato → 400; codigos bien formados pero desconocidos (XYZ) se registran tal cual |
| redeemedAt | string | Fecha-hora ISO-8601 del canje, entre 2024-01-01 y maximo 48 h en el futuro. Ausente → ahora |
| externalReference | string | Referencia tuya (numero de boleta, id de orden...). Opcional |
| override | boolean | true (boolean JSON, no el string "true") para sobreescribir un canje ya registrado (ver Canje unico) |
Los montos son enteros en unidades menores de la moneda: 15990 con currency: "CLP" son $15.990 (el peso chileno no usa decimales); 15990 con currency: "USD" son US$159,90.
La validacion de currency es de forma, no de catalogo: cualquier codigo de 3 letras mayusculas se acepta y se registra tal cual, aunque no sea un codigo ISO-4217 real ("XYZ" pasa). Lo que no cumple la forma ("clp", "PESO", 152) responde 400.
Ejemplo
curl -X POST https://southgames.ai/api/v1/inapp-games/redemptions \
-H "Authorization: Bearer sg_live_xxx" \
-H "X-Org-Id: mi-empresa" \
-H "Content-Type: application/json" \
-d '{
"notificationId": "notif_abc123",
"awardId": "2026-08_cliente42",
"saleTotal": 15990,
"discountAmount": 3000,
"currency": "CLP",
"redeemedAt": "2026-08-05T14:30:00Z",
"externalReference": "boleta-778899"
}'
Respuesta (200)
{
"ok": true,
"awardId": "2026-08_cliente42",
"redemption": {
"saleTotal": 15990,
"discountAmount": 3000,
"currency": "CLP",
"redeemedAt": "2026-08-05T14:30:00.000Z",
"externalReference": "boleta-778899"
}
}
Identificacion alternativa (sin awardId)
Muchos backends no persisten nuestro awardId: guardan su id de usuario y su id de premio. Para esos casos el endpoint acepta, en lugar de awardId, el par:
clientExternalId— elexternalIdcon el que registras al usuario en SouthGames (el mismo que mandas enidentify/register).segmentExternalId— el codigo del gajo que declaraste en el editor de la ruleta (campo "Id externo" del gajo). Es el mismo valor que viaja comosegmentExternalIden el webhookinapp_game.wony comoexternalIden el resultado del SDK.
| Parametro | Tipo | Descripcion |
|---|---|---|
| notificationId* | string | Id de la notificacion de mini-juego. Igual que en el modo awardId |
| clientExternalId* | string | Tu id de usuario (el externalId del cliente en SouthGames). String no vacio, maximo 200 caracteres. Se le aplica trim; acepta cualquier caracter (@, /, acentos) |
| segmentExternalId* | string | Tu codigo del gajo premiado (el Id externo declarado en el editor). String no vacio, maximo 200 caracteres, con trim |
El resto del body (saleTotal, discountAmount, currency, redeemedAt, externalReference, override) es identico y se valida igual.
Regla XOR: manda awardId o el par clientExternalId + segmentExternalId, nunca los dos. Los dos juntos, ninguno de los dos, o solo uno de la pareja responden 400.
Regla FIFO: si el cliente gano ese mismo gajo varias veces, se canjea el premio mas antiguo que aun no tenga canje (el cliente canjea primero lo que gano primero); el siguiente reporte tomara el que sigue.
Como se busca el premio: el id de un award es siempre {periodo}_{clientId}, asi que la resolucion construye las claves de periodo de la campana (segun su cadencia de giro: sin renovacion, diaria, semanal o mensual) desde que la campana existe hasta hoy, y lee esos ids directamente. El resultado es exacto y su costo depende del numero de periodos de la campana, nunca de cuantos premios lleva repartidos.
Limite de periodos: una campana que acumule mas de 400 periodos de giro (por ejemplo, una diaria de mas de 13 meses, o una en cadencia minute de QA que lleve horas corriendo) responde 409 TOO_MANY_PERIODS. No es una degradacion silenciosa: para esas campanas usa el modo awardId, que no tiene este limite.
{
"error": "Esta campana tiene mas de 400 periodos de giro: usa awardId para reportar sus canjes",
"code": "TOO_MANY_PERIODS"
}
curl -X POST https://southgames.ai/api/v1/inapp-games/redemptions \
-H "Authorization: Bearer sg_live_xxx" \
-H "X-Org-Id: mi-empresa" \
-H "Content-Type: application/json" \
-d '{
"notificationId": "notif_abc123",
"clientExternalId": "usuario-9931",
"segmentExternalId": "SKU-GIFTCARD-5000",
"saleTotal": 15990,
"discountAmount": 3000,
"currency": "CLP",
"redeemedAt": "2026-08-05T14:30:00Z",
"externalReference": "boleta-778899"
}'
La respuesta 200 es la misma del modo clasico e incluye el awardId resuelto, para que sepas exactamente que premio quedo marcado (y puedas corregirlo despues con override usando ese awardId):
{
"ok": true,
"awardId": "2026-08_HkQ2v9xTa1",
"redemption": {
"saleTotal": 15990,
"discountAmount": 3000,
"currency": "CLP",
"redeemedAt": "2026-08-05T14:30:00.000Z",
"externalReference": "boleta-778899"
}
}
Si todos los premios del cliente para ese gajo ya tienen canje, la respuesta es 409 ALL_REDEEMED con el ultimo canje conocido y su awardId:
{
"error": "Todos los premios de ese gajo para este cliente ya tienen canje registrado",
"code": "ALL_REDEEMED",
"awardId": "2026-08_HkQ2v9xTa1",
"redemption": {
"saleTotal": 12990,
"discountAmount": 2000,
"currency": "CLP",
"redeemedAt": "2026-08-04T10:00:00.000Z",
"externalReference": "boleta-1",
"recordedAt": "2026-08-04T10:00:05.000Z"
}
}
Para corregir un canje ya registrado (override: true) usa el modo awardId con el id que te devolvio el 200 o el 409: el modo alternativo siempre apunta al proximo premio sin canjear, nunca a uno ya reportado.
Errores propios de este modo:
| Codigo | HTTP | Descripcion |
|---|---|---|
CLIENT_NOT_FOUND | 404 | Ningun cliente de la organizacion tiene ese clientExternalId (si el cliente fue fusionado, se resuelve solo al cliente vigente) |
SEGMENT_NOT_FOUND | 404 | Ningun gajo de la ruleta declara ese segmentExternalId en la configuracion vigente |
AWARD_NOT_FOUND | 404 | El cliente existe pero no gano ese gajo en este mini-juego |
ALL_REDEEMED | 409 | Todos los premios del cliente para ese gajo ya tienen canje |
AMBIGUOUS_SEGMENT | 409 | Dos gajos comparten el mismo segmentExternalId: usa awardId para desambiguar |
TOO_MANY_PERIODS | 409 | La campana acumula mas de 400 periodos de giro: usa awardId para reportar sus canjes |
Canje unico (409 y override)
Cada premio admite un solo canje. Si el award ya tiene un canje registrado, un segundo reporte responde 409 con el canje existente en el body — asi puedes reconciliar sin otra llamada:
# Segundo reporte del mismo premio, sin override
curl -X POST https://southgames.ai/api/v1/inapp-games/redemptions \
-H "Authorization: Bearer sg_live_xxx" \
-H "X-Org-Id: mi-empresa" \
-H "Content-Type: application/json" \
-d '{
"notificationId": "notif_abc123",
"awardId": "2026-08_cliente42",
"saleTotal": 12990,
"discountAmount": 2000,
"currency": "CLP"
}'
Respuesta 409:
{
"error": "El premio ya tiene un canje registrado",
"code": "ALREADY_REDEEMED",
"redemption": {
"saleTotal": 9990,
"discountAmount": 1000,
"currency": "CLP",
"redeemedAt": "2026-08-02T18:00:00.000Z",
"externalReference": "boleta-1",
"recordedAt": "2026-08-02T18:00:05.000Z"
}
}
Para corregir un canje mal reportado, repite el POST con "override": true: el canje nuevo reemplaza al vigente y el anterior queda guardado en redemption.previous (una sola generacion de historia — un segundo override reemplaza previous, no lo anida):
curl -X POST https://southgames.ai/api/v1/inapp-games/redemptions \
-H "Authorization: Bearer sg_live_xxx" \
-H "X-Org-Id: mi-empresa" \
-H "Content-Type: application/json" \
-d '{
"notificationId": "notif_abc123",
"awardId": "2026-08_cliente42",
"saleTotal": 20000,
"discountAmount": 5000,
"currency": "CLP",
"override": true
}'
La respuesta del override incluye el canje anterior en redemption.previous.
Gajos que no son premio (422)
Solo los premios se canjean: si el gajo del award esta marcado explicitamente como no premio (isPrize: false, un "sigue participando"), el endpoint responde 422:
# El awardId apunta a un gajo "sigue participando" (isPrize: false)
curl -X POST https://southgames.ai/api/v1/inapp-games/redemptions \
-H "Authorization: Bearer sg_live_xxx" \
-H "X-Org-Id: mi-empresa" \
-H "Content-Type: application/json" \
-d '{
"notificationId": "notif_abc123",
"awardId": "2026-08_cliente77",
"saleTotal": 8990,
"discountAmount": 0,
"currency": "CLP"
}'
Respuesta 422:
{
"error": "El gajo de este premio no es canjeable (no es premio)",
"code": "NOT_A_PRIZE"
}
Si el gajo del award ya no existe en la config vigente de la ruleta (fue renombrado o eliminado despues del giro), el canje se acepta: el premio historico es legitimo.
Errores
| Codigo | HTTP | Descripcion |
|---|---|---|
VALIDATION_ERROR | 400 | Body invalido: montos negativos, no enteros o sobre 2^53-1; currency que no son 3 letras mayusculas; redeemedAt malformado o fuera de rango (antes de 2024-01-01 o a mas de 48 h en el futuro); ids faltantes, con caracteres invalidos o de mas de 200 caracteres; override no-boolean; awardId junto al par de ids externos, ninguno de los dos, o solo uno de la pareja |
MISSING_API_KEY / INVALID_API_KEY | 401 | Sin API key o key invalida/inactiva |
NOT_FOUND | 404 | La notificacion o el award no existen (o pertenecen a otra organizacion) |
CLIENT_NOT_FOUND | 404 | Identificacion alternativa: no hay cliente con ese clientExternalId |
SEGMENT_NOT_FOUND | 404 | Identificacion alternativa: ningun gajo declara ese segmentExternalId |
AWARD_NOT_FOUND | 404 | Identificacion alternativa: el cliente no gano ese gajo |
ALREADY_REDEEMED | 409 | El premio ya tiene un canje registrado y no se envio override: true (el canje existente viaja en el body) |
ALL_REDEEMED | 409 | Identificacion alternativa: todos los premios del cliente para ese gajo ya tienen canje |
AMBIGUOUS_SEGMENT | 409 | Identificacion alternativa: dos gajos comparten el mismo segmentExternalId |
TOO_MANY_PERIODS | 409 | Identificacion alternativa: la campana acumula mas de 400 periodos de giro (usa awardId) |
NOT_A_PRIZE | 422 | El gajo del award esta marcado como no premio en la config vigente |