Ingresos de fondos
Allaria detecta los ingresos de fondos en cuentas Allaria y notifica a los
consumidores suscriptos mediante un webhook saliente. Cuando ingresan fondos a
una cuenta asociada al consumidor, Allaria envía un POST al endpoint provisto
por el consumidor con el payload descripto más abajo.
¿Buscás los ingresos a cuentas Allaria+ (billetera)? Esos los notifica un webhook aparte — ver Escuchar ingresos a cuentas.
Para sumar nuevos consumidores con distintos filtros y campos notificados se debe coordinar con el equipo responsable del webhook.
La notificación
Se notifica cada vez que ingresan fondos a una cuenta Allaria asociada al consumidor. Los ingresos a otras cuentas no disparan el webhook.
A efectos de este webhook, los Recibos de Cobro se consideran ingresos de fondos a la cuenta Allaria asociada.
Los campos que no aplican igual viajan en el payload, en null: no se omiten.
Parseá el payload de forma tolerante: ignorá los campos que no necesitás y no
trates un campo desconocido como error, porque con el tiempo pueden agregarse
nuevos.
Payload
{
"origin": "ALLARIA",
"amount": 150000.00,
"cvu": null,
"currency": "ARS",
"event_id": "3f2a9c1e-7b64-4d51-9a08-1c2f5e6d7b90",
"target_id": 20123456
}
| Campo | Tipo | Descripción |
|---|---|---|
origin | string | Identifica la fuente del ingreso. Valor fijo: "ALLARIA". |
amount | number | Monto del ingreso, en la moneda de la cuenta destino. |
cvu | null | No aplica a este escenario. Siempre null. |
currency | string | Identificador del tipo de moneda ("ARS", "USD", etc.). |
event_id | string | UUID que identifica el evento. Estable entre reintentos: usalo para deduplicar. |
target_id | number | Número de cuenta Allaria asociada al cliente destino del ingreso. |
Resumen de disparadores
| Origen del ingreso | Condición para disparar | Identificador en el payload |
|---|---|---|
| Acreditación / Recibo de Cobro | Número de cuenta correspondiente al consumidor | target_id (número de cuenta) |
Firma del webhook
Cada notificación se envía firmada, para que puedas verificar que la envió Allaria y que el body no fue alterado en tránsito. Esto importa porque el endpoint que recibe estas notificaciones es alcanzable desde internet: sin validar la firma, cualquiera que conozca su URL puede postear un ingreso que parezca legítimo.
HTTPS por sí solo no cubre esto: protege el request en tránsito y autentica al servidor, pero no dice nada sobre quién originó el request.
La firma se habilita por consumidor y se coordina con el equipo responsable del webhook: primero implementás la validación, después te entregamos el secreto compartido por un canal seguro, y recién ahí empezamos a firmar tus notificaciones. Hasta que eso se acuerde, los headers descriptos abajo no se envían.
Headers
Cada POST incluye dos headers adicionales:
X-Allaria-Timestamp: 1790000000
X-Allaria-Signature: v1=75748751a43a167440a3fdeac209f2ba8c200640027d2234f0d242655ada271c
| Campo | Tipo | Descripción |
|---|---|---|
X-Allaria-Timestamp | string | Momento en que se firmó el request, en segundos desde el epoch Unix (UTC). |
X-Allaria-Signature | string | Versión del esquema de firma y la firma: v1= seguido de 64 caracteres hexadecimales en minúscula. |
El prefijo v1 versiona el esquema. Si en algún momento cambia el algoritmo, va
a aparecer un prefijo distinto: tratá un prefijo desconocido como inválido en
lugar de ignorarlo.
Cómo se construye la firma
signed_payload = timestamp + "." + body
signature = HMAC_SHA256(signed_payload, secret) → hex en minúscula
Donde timestamp es el string exacto enviado en X-Allaria-Timestamp, body
son los bytes crudos del cuerpo del request tal como se transmiten, y secret es
el secreto compartido que te entregamos, en UTF-8. El separador es un punto.
Cómo validar un request
- Leé el body crudo, antes de cualquier parseo de JSON. No lo parsees para volver a serializarlo: ver la advertencia más abajo.
- Rechazá el request si falta cualquiera de los dos headers, o si la firma no
empieza con
v1=. - Compará
X-Allaria-Timestampcontra tu hora actual y rechazá el request si la diferencia es mayor a 5 minutos. Esto es lo que impide que un request capturado se reenvíe más tarde. - Recalculá la firma y compará con una comparación en tiempo constante
(
crypto.timingSafeEqual,hmac.compare_digest,MessageDigest.isEqual). - Respondé
401cuando la verificación falla, y procesá la notificación sólo cuando es exitosa.
Una notificación fallida se reintenta hasta tres veces, y cada reintento se
firma de nuevo, con timestamp y firma nuevos. Deduplicá por el event_id del
payload, que es estable entre intentos, nunca por la firma ni por el timestamp,
que cambian cada vez.
Vector de prueba
Usalo para verificar tu implementación antes de que se intercambie el secreto. El body es arbitrario: la firma se calcula sobre bytes, así que cualquier body funciona igual.
secret = test_secret_do_not_use_in_production
timestamp = 1790000000
body = {"example":"payload"}
signed_payload = 1790000000.{"example":"payload"}
signature = 75748751a43a167440a3fdeac209f2ba8c200640027d2234f0d242655ada271c
Si tu código produce esa firma con esos valores, está bien. Podés verificarlo desde una terminal con:
printf '%s' '1790000000.{"example":"payload"}' \
| openssl dgst -sha256 -hmac 'test_secret_do_not_use_in_production'
Tené en cuenta que un receptor bien implementado tiene que rechazar un request real con ese timestamp, porque queda fuera de la ventana de frescura. Ese también es un caso de prueba válido.
Errores comunes
La mayoría de las integraciones que fallan validan la firma contra un body que
fue parseado y serializado de nuevo. JSON.parse + JSON.stringify, o bindear
el request directo a un objeto tipado, pueden cambiar el orden de las claves, los
espacios o la notación de los decimales, y la firma nunca va a coincidir. Muchos
frameworks consumen el body antes de que lo veas: en Express, capturalo con la
opción verify de express.json(); en Spring, recibí el body como String en
lugar de un DTO.
También conviene revisar:
- No compares firmas con
==ni.equals(). Una comparación en tiempo constante evita filtrar el valor esperado a través del tiempo de respuesta. - No omitas el chequeo de timestamp. Sin él, un request capturado sigue siendo válido para siempre.
- No confíes en el header de timestamp por separado. Está cubierto por la
firma, y por eso forma parte del
signed_payload: leerlo sin verificar la firma no prueba nada. - Nunca loguees el secreto ni los headers de firma, en logs de acceso o de debug.
- Una firma válida es autenticación, no autorización. Prueba que la notificación es genuinamente nuestra; no te exime de validar montos, cuentas y duplicados.
Recuperar notificaciones perdidas
Si tu endpoint de webhook estuvo inalcanzable, una notificación puede quedar en estado fallido después de agotar sus reintentos. Estos dos endpoints te permiten listar las notificaciones que fallaron y pedirle a Allaria que las envíe de nuevo. Ambos se autentican con tu access token (ver Primeros pasos) y sólo operan sobre tus propias notificaciones.
Listar notificaciones fallidas
/v1/outbound-notifier/notifications/failedDevuelve los event_id de los eventos cuya notificación hacia vos terminó en
estado fallido, dentro de un rango de fechas.
Request
- cURL
- JavaScript
- Python
- Node.js
curl -X GET "https://b2b.allaria.cloud/v1/outbound-notifier/notifications/failed?origin=ALLARIA&from=2024-01-01&to=2024-01-31" \
-H "Authorization: Bearer ACCESS_TOKEN"
const params = new URLSearchParams({
origin: 'ALLARIA',
from: '2024-01-01',
to: '2024-01-31',
});
const response = await fetch(`https://b2b.allaria.cloud/v1/outbound-notifier/notifications/failed?${params}`, {
method: 'GET',
headers: { 'Authorization': 'Bearer ACCESS_TOKEN' },
});
const data = await response.json();
console.log(data);
import requests
response = requests.get(
'https://b2b.allaria.cloud/v1/outbound-notifier/notifications/failed',
headers={ 'Authorization': 'Bearer ACCESS_TOKEN' },
params={ 'origin': 'ALLARIA', 'from': '2024-01-01', 'to': '2024-01-31' },
)
data = response.json()
print(data)
const axios = require('axios');
const response = await axios.get('https://b2b.allaria.cloud/v1/outbound-notifier/notifications/failed', {
headers: { Authorization: 'Bearer ACCESS_TOKEN' },
params: { origin: 'ALLARIA', from: '2024-01-01', to: '2024-01-31' },
});
console.log(response.data);
Headers
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
Authorization | string | Sí | Access token en formato Bearer ACCESS_TOKEN |
Parámetros de query
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
origin | string | Sí | Fuente de los eventos. Para este webhook, ALLARIA. |
from | string (date) | No | Inicio del rango, YYYY-MM-DD (inclusive, horario de Buenos Aires). Por defecto hoy. |
to | string (date) | No | Fin del rango, YYYY-MM-DD (inclusive, horario de Buenos Aires). Por defecto hoy. |
Response
2XX Success
Status: 200 OK
{
"event_ids": [
"3f2a9c1e-7b64-4d51-9a08-1c2f5e6d7b90",
"a1b2c3d4-e5f6-7890-abcd-ef1234567890"
]
}
| Campo | Tipo | Descripción |
|---|---|---|
event_ids | array[string (uuid)] | UUIDs de los eventos cuya notificación hacia vos terminó en estado fallido. Es el mismo event_id del payload del webhook. Vacío cuando no hay ninguna. |
Códigos de error
| Status | Descripción |
|---|---|
400 | Bad Request — origin ausente o inválido, o from posterior a to |
401 | Unauthorized — access token inválido o ausente |
403 | Forbidden — el token no está autorizado para este webhook |
Reprocesar notificaciones
/v1/outbound-notifier/notifications/reprocessEncola las notificaciones fallidas de los eventos indicados para enviarlas de
nuevo. El reprocesamiento es asíncrono: un 202 significa que el pedido fue
aceptado, no que la entrega haya terminado. Cada notificación encolada se
reintenta como la original (hasta tres intentos) y el reenvío llega a tu endpoint
de webhook firmado como cualquier otra notificación — deduplicá por event_id.
Request
- cURL
- JavaScript
- Python
- Node.js
curl -X POST "https://b2b.allaria.cloud/v1/outbound-notifier/notifications/reprocess" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"origin": "ALLARIA",
"event_ids": [
"3f2a9c1e-7b64-4d51-9a08-1c2f5e6d7b90",
"a1b2c3d4-e5f6-7890-abcd-ef1234567890"
]
}'
const response = await fetch('https://b2b.allaria.cloud/v1/outbound-notifier/notifications/reprocess', {
method: 'POST',
headers: {
'Authorization': 'Bearer ACCESS_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
origin: 'ALLARIA',
event_ids: [
'3f2a9c1e-7b64-4d51-9a08-1c2f5e6d7b90',
'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
],
}),
});
const data = await response.json();
console.log(data);
import requests
response = requests.post(
'https://b2b.allaria.cloud/v1/outbound-notifier/notifications/reprocess',
headers={
'Authorization': 'Bearer ACCESS_TOKEN',
'Content-Type': 'application/json',
},
json={
'origin': 'ALLARIA',
'event_ids': [
'3f2a9c1e-7b64-4d51-9a08-1c2f5e6d7b90',
'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
],
},
)
data = response.json()
print(data)
const axios = require('axios');
const response = await axios.post(
'https://b2b.allaria.cloud/v1/outbound-notifier/notifications/reprocess',
{
origin: 'ALLARIA',
event_ids: [
'3f2a9c1e-7b64-4d51-9a08-1c2f5e6d7b90',
'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
],
},
{
headers: {
Authorization: 'Bearer ACCESS_TOKEN',
'Content-Type': 'application/json',
},
}
);
console.log(response.data);
Headers
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
Authorization | string | Sí | Access token en formato Bearer ACCESS_TOKEN |
Content-Type | string | Sí | Debe ser application/json |
Body
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
origin | string | Sí | Fuente de los eventos. Para este webhook, ALLARIA. |
event_ids | array[string (uuid)] | Sí | Hasta 100 UUIDs de eventos a reprocesar. Los duplicados se ignoran. |
Response
2XX Success
Status: 202 Accepted
{
"reprocessed": [
"3f2a9c1e-7b64-4d51-9a08-1c2f5e6d7b90"
],
"rejected": [
{
"event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"reason": "NOT_FAILED"
}
]
}
| Campo | Tipo | Descripción |
|---|---|---|
reprocessed | array[string (uuid)] | Eventos cuya notificación fallida fue encolada para enviarse de nuevo. |
rejected | array[object] | Eventos que no se reprocesaron, cada uno con su motivo. |
rejected[].event_id | string (uuid) | El evento rechazado. |
rejected[].reason | string | NOT_FOUND (no es un evento fallido tuyo) o NOT_FAILED (la notificación no está en estado fallido). |
Códigos de error
| Status | Descripción |
|---|---|
400 | Bad Request — origin inválido, event_ids vacío o ausente, un UUID malformado, o más de 100 ids |
401 | Unauthorized — access token inválido o ausente |
403 | Forbidden — el token no está autorizado para este webhook |
409 | Conflict — las notificaciones a tu integración están deshabilitadas |