Fund deposits
Allaria detects fund deposits into Allaria accounts and notifies subscribed
consumers through an outbound webhook. When funds are deposited into an account
associated with the consumer, Allaria sends a POST to the endpoint provided by
the consumer with the payload described below.
Looking for deposits into Allaria+ (wallet) accounts? Those are notified by a separate webhook — see Listen for account deposits.
To add new consumers with different filters and notified fields, you must coordinate with the team responsible for the webhook.
The notification
A notification is sent every time funds are deposited into an Allaria account associated with the consumer. Deposits into other accounts do not trigger the webhook.
For the purposes of this webhook, Collection Receipts are considered fund deposits into the associated Allaria account.
Fields that do not apply are still present in the payload, set to null — they
are not omitted. Parse the payload leniently: ignore the fields you do not need,
and do not treat an unknown field as an error, since new ones may be added over
time.
Payload
{
"origin": "ALLARIA",
"amount": 150000.00,
"cvu": null,
"currency": "ARS",
"event_id": "3f2a9c1e-7b64-4d51-9a08-1c2f5e6d7b90",
"target_id": 20123456
}
| Field | Type | Description |
|---|---|---|
origin | string | Identifies the source of the deposit. Fixed value: "ALLARIA". |
amount | number | Deposit amount, in the target account's currency. |
cvu | null | Does not apply to this scenario. Always null. |
currency | string | Currency type identifier ("ARS", "USD", etc.). |
event_id | string | UUID identifying the event. Stable across retries — use it to deduplicate. |
target_id | number | Allaria account number associated with the deposit's target client. |
Trigger summary
| Deposit source | Trigger condition | Identifier in the payload |
|---|---|---|
| Credit / Collection Receipt | Account number belonging to the consumer | target_id (account number) |
Webhook signature
Every notification is signed, so you can verify that Allaria sent it and that the body was not altered in transit. This matters because the endpoint that receives these notifications is reachable over the internet: without verifying the signature, anyone who learns its URL can post a deposit that looks legitimate.
HTTPS alone does not cover this — it protects the request in transit and authenticates the server, but says nothing about who sent the request.
Signing is enabled per consumer and coordinated with the team responsible for the webhook: you implement the validation first, we hand over the shared secret through a secure channel, and only then do we start signing your notifications. Until that is agreed, the headers below are not sent.
Headers
Each POST carries two additional headers:
X-Allaria-Timestamp: 1790000000
X-Allaria-Signature: v1=75748751a43a167440a3fdeac209f2ba8c200640027d2234f0d242655ada271c
| Field | Type | Description |
|---|---|---|
X-Allaria-Timestamp | string | Time the request was signed, in seconds since the Unix epoch (UTC). |
X-Allaria-Signature | string | Signature scheme version, then the signature: v1= followed by 64 lowercase hexadecimal characters. |
The v1 prefix versions the scheme. If the algorithm ever changes, a different
prefix will appear — treat an unknown prefix as invalid rather than ignoring it.
How the signature is built
signed_payload = timestamp + "." + body
signature = HMAC_SHA256(signed_payload, secret) → lowercase hex
Where timestamp is the exact string sent in X-Allaria-Timestamp, body is the
raw bytes of the request body as transmitted, and secret is the shared secret
issued to you, in UTF-8. The separator is a single dot.
Validating a request
- Read the raw request body, before any JSON parsing. Do not parse and re-serialize it — see the warning below.
- Reject the request if either header is missing, or if the signature does not
start with
v1=. - Compare
X-Allaria-Timestampagainst your current time and reject the request if the difference is greater than 5 minutes. This is what prevents a captured request from being replayed later. - Recompute the signature and compare it using a constant-time comparison
(
crypto.timingSafeEqual,hmac.compare_digest,MessageDigest.isEqual). - Respond
401when verification fails, and process the notification only when it succeeds.
A failed notification is retried up to three times, and each retry is signed
again with a fresh timestamp and signature. Deduplicate on the payload's
event_id, which is stable across attempts — never on the signature or the
timestamp, which change every time.
Test vector
Use this to confirm your implementation before the secret is exchanged. The body below is arbitrary — the signature is computed over bytes, so any body works the same way.
secret = test_secret_do_not_use_in_production
timestamp = 1790000000
body = {"example":"payload"}
signed_payload = 1790000000.{"example":"payload"}
signature = 75748751a43a167440a3fdeac209f2ba8c200640027d2234f0d242655ada271c
If your code produces that signature from those inputs, it is correct. You can check it from a terminal with:
printf '%s' '1790000000.{"example":"payload"}' \
| openssl dgst -sha256 -hmac 'test_secret_do_not_use_in_production'
Note that a well-implemented receiver must reject a live request carrying this timestamp, because it falls outside the freshness window. That is also a valid test.
Common mistakes
Most failed integrations come from validating the signature against a body that
was parsed and serialized again. JSON.parse + JSON.stringify, or binding the
request straight to a typed object, can change key order, whitespace or decimal
notation, and the resulting signature will never match. Frameworks often consume
the body before your code sees it — in Express, capture it with the verify
option of express.json(); in Spring, receive the body as a String rather than
a DTO.
Also worth checking:
- Do not compare signatures with
==or.equals(). A constant-time comparison avoids leaking the expected value through response timing. - Do not skip the timestamp check. Without it, a captured request stays valid forever.
- Do not trust the timestamp header on its own. It is covered by the
signature, which is why it must be part of
signed_payload— reading it without verifying the signature proves nothing. - Never log the secret, or the signature headers, in access or debug logs.
- A valid signature is authentication, not authorization. It proves the notification is genuinely ours; it does not exempt you from validating amounts, accounts and duplicates.
Recover missed notifications
If your webhook endpoint was unreachable, a notification can end up failed after its retries are exhausted. These two endpoints let you list the notifications that failed and ask Allaria to send them again. Both are authenticated with your access token (see Get started) and only ever act on your own notifications.
List failed notifications
/v1/outbound-notifier/notifications/failedReturns the event_ids of the events whose notification to you ended in a failed
state, within a date range.
Request
- cURL
- JavaScript
- Python
- Node.js
curl -X GET "https://b2b.allaria.dev/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.dev/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.dev/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.dev/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
| Field | Type | Required | Description |
|---|---|---|---|
Authorization | string | Yes | Access token in Bearer ACCESS_TOKEN format |
Query Parameters
| Field | Type | Required | Description |
|---|---|---|---|
origin | string | Yes | Source of the events. For this webhook, ALLARIA. |
from | string (date) | No | Start of the range, YYYY-MM-DD (inclusive, Buenos Aires time). Defaults to today. |
to | string (date) | No | End of the range, YYYY-MM-DD (inclusive, Buenos Aires time). Defaults to today. |
Response
2XX Success
Status: 200 OK
{
"event_ids": [
"3f2a9c1e-7b64-4d51-9a08-1c2f5e6d7b90",
"a1b2c3d4-e5f6-7890-abcd-ef1234567890"
]
}
| Field | Type | Description |
|---|---|---|
event_ids | array[string (uuid)] | UUIDs of the events whose notification to you ended in a failed state. Same event_id as in the webhook payload. Empty when there are none. |
Error Codes
| Status | Description |
|---|---|
400 | Bad Request — missing or invalid origin, or from is after to |
401 | Unauthorized — invalid or missing access token |
403 | Forbidden — the token is not authorized for this webhook |
Reprocess notifications
/v1/outbound-notifier/notifications/reprocessQueues the failed notifications of the given events to be sent again. Reprocessing
is asynchronous: a 202 means the request was accepted, not that delivery has
finished. Each queued notification is retried like the original (up to three
attempts) and the resend reaches your webhook endpoint signed like any other
notification — deduplicate by event_id.
Request
- cURL
- JavaScript
- Python
- Node.js
curl -X POST "https://b2b.allaria.dev/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.dev/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.dev/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.dev/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
| Field | Type | Required | Description |
|---|---|---|---|
Authorization | string | Yes | Access token in Bearer ACCESS_TOKEN format |
Content-Type | string | Yes | Must be application/json |
Body
| Field | Type | Required | Description |
|---|---|---|---|
origin | string | Yes | Source of the events. For this webhook, ALLARIA. |
event_ids | array[string (uuid)] | Yes | Up to 100 event UUIDs to reprocess. Duplicates are ignored. |
Response
2XX Success
Status: 202 Accepted
{
"reprocessed": [
"3f2a9c1e-7b64-4d51-9a08-1c2f5e6d7b90"
],
"rejected": [
{
"event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"reason": "NOT_FAILED"
}
]
}
| Field | Type | Description |
|---|---|---|
reprocessed | array[string (uuid)] | Events whose failed notification was queued to be sent again. |
rejected | array[object] | Events that were not reprocessed, each with a reason. |
rejected[].event_id | string (uuid) | The rejected event. |
rejected[].reason | string | NOT_FOUND (not a failed event of yours) or NOT_FAILED (the notification is not in a failed state). |
Error Codes
| Status | Description |
|---|---|
400 | Bad Request — invalid origin, empty or missing event_ids, a malformed UUID, or more than 100 ids |
401 | Unauthorized — invalid or missing access token |
403 | Forbidden — the token is not authorized for this webhook |
409 | Conflict — notifications to your integration are disabled |