Reportar canje de premio

Ingesta server-to-server de canjes de premios de mini-juegos in-app.

POST/api/v1/inapp-games/redemptions

Registra 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 trae notificationId y awardId del premio recien ganado. Ver Webhooks > Eventos.
  • Resultado del SDK (onGameResult / evento result:): el mismo awardId viaja 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

ParametroTipoDescripcion
notificationId*stringId de la notificacion de mini-juego (viene en el webhook inapp_game.won). Letras, numeros, _ : -, maximo 200 caracteres
awardId*stringId del premio a canjear (mismo awardId del webhook / resultado del SDK). Letras, numeros, _ : -, maximo 200 caracteres. Alternativa: omitelo y envia clientExternalId + segmentExternalId
saleTotal*intTotal 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*intDescuento efectivamente aplicado, entero >= 0 en unidades menores de la moneda. Maximo 2^53-1
currency*stringCodigo de moneda de 3 letras mayusculas (CLP, USD, EUR...). Otro formato → 400; codigos bien formados pero desconocidos (XYZ) se registran tal cual
redeemedAtstringFecha-hora ISO-8601 del canje, entre 2024-01-01 y maximo 48 h en el futuro. Ausente → ahora
externalReferencestringReferencia tuya (numero de boleta, id de orden...). Opcional
overridebooleantrue (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 — el externalId con el que registras al usuario en SouthGames (el mismo que mandas en identify / 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 como segmentExternalId en el webhook inapp_game.won y como externalId en el resultado del SDK.
ParametroTipoDescripcion
notificationId*stringId de la notificacion de mini-juego. Igual que en el modo awardId
clientExternalId*stringTu id de usuario (el externalId del cliente en SouthGames). String no vacio, maximo 200 caracteres. Se le aplica trim; acepta cualquier caracter (@, /, acentos)
segmentExternalId*stringTu 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:

CodigoHTTPDescripcion
CLIENT_NOT_FOUND404Ningun cliente de la organizacion tiene ese clientExternalId (si el cliente fue fusionado, se resuelve solo al cliente vigente)
SEGMENT_NOT_FOUND404Ningun gajo de la ruleta declara ese segmentExternalId en la configuracion vigente
AWARD_NOT_FOUND404El cliente existe pero no gano ese gajo en este mini-juego
ALL_REDEEMED409Todos los premios del cliente para ese gajo ya tienen canje
AMBIGUOUS_SEGMENT409Dos gajos comparten el mismo segmentExternalId: usa awardId para desambiguar
TOO_MANY_PERIODS409La 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

CodigoHTTPDescripcion
VALIDATION_ERROR400Body 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_KEY401Sin API key o key invalida/inactiva
NOT_FOUND404La notificacion o el award no existen (o pertenecen a otra organizacion)
CLIENT_NOT_FOUND404Identificacion alternativa: no hay cliente con ese clientExternalId
SEGMENT_NOT_FOUND404Identificacion alternativa: ningun gajo declara ese segmentExternalId
AWARD_NOT_FOUND404Identificacion alternativa: el cliente no gano ese gajo
ALREADY_REDEEMED409El premio ya tiene un canje registrado y no se envio override: true (el canje existente viaja en el body)
ALL_REDEEMED409Identificacion alternativa: todos los premios del cliente para ese gajo ya tienen canje
AMBIGUOUS_SEGMENT409Identificacion alternativa: dos gajos comparten el mismo segmentExternalId
TOO_MANY_PERIODS409Identificacion alternativa: la campana acumula mas de 400 periodos de giro (usa awardId)
NOT_A_PRIZE422El gajo del award esta marcado como no premio en la config vigente