Deposit/Webhooks
Webhook Security, Event Types, & Best Practices
Webhook Signature Verification
To ensure authenticity, Acta signs every webhook request using HMAC-SHA256. You must verify this signature to confirm that the payload came from Acta and wasn't tampered with.
Headers in Every Webhook Request:
Header | Description |
---|---|
x-actalink-signature | Final HMAC-SHA256 signature of the timestamp and payload |
x-actalink-timestamp | Unix timestamp (in milliseconds) used in the signature |
How to Verify the Webhook Signature:
Extract the following from the request
x-actalink-signature
header (Acta's signature)x-actalink-timestamp
header (timestamp used during signing)- Raw JSON request body (exactly as received, not parsed)
Recompute the signature
- Wrap the raw payload in an object.
- Serialize it with
JSON.stringify()
(no added whitespace). - Compute HMAC-SHA256 of the stringified wrapper using your webhook secret.
- Concatenate the timestamp + "." + that hash.
- Compute HMAC-SHA256 of that final string.
Compare your computed signature with the one from x-actalink-signature.
Optionally, reject the webhook if the timestamp is too old (e.g. older than 5 minutes) to prevent replay attacks.
Node.js Example
const crypto = require("crypto");
function verifyWebhookSignature({
rawBody,
timestamp,
receivedSignature,
secret,
}) {
const payload = JSON.parse(rawBody);
const wrapped = JSON.stringify({ payload });
const intermediateSig = crypto
.createHmac("sha256", secret)
.update(wrapped)
.digest("hex");
const finalSig = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${intermediateSig}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(finalSig),
Buffer.from(receivedSignature)
);
}
const rawBody = JSON.stringify({
id: "c837a151-a962-44e0-b3e3-b4f61743d7bb",
eventType: "recurring.deposit.due",
eventData: {
data: {
id: "d130d138-cfa3-4dbb-acc5-291a1e4b79ea",
token: {
name: "USD Coin",
amount: "0x249f0",
symbol: "USDC",
address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
chainId: 137,
fiatISO: "USD",
logoURI: "https://api.acta.link/deposit/v1/logos/usdc.png",
decimals: 6,
logoSourceURI: "https://api.acta.link/deposit/v1/logos/usdc.png",
},
reason: "BALANCE",
status: "DUE",
chainId: 137,
network: {
name: "Polygon",
chainId: 137,
},
attempts: 0,
currency: "USDC",
transaction: null,
feeInclusive: false,
intervalUnit: "5mins",
tokenAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
intervalCount: 2,
senderAddress: "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
nextExecutionAt: "2025-08-16T14:27:02.098Z",
receiverAddress: "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB",
effectedInterval: 1,
expectedExecutionAt: "2025-08-16T14:20:55.000Z",
latestIntervalCount: 1,
},
type: "recurring",
projectId: "dcb827a2-8b5e-48af-9271-05c47b231555",
organisationId: "SwHuo5b9UCc43wFCphlC0uQCkQQVvcga",
},
createdAt: "2025-08-16T14:22:02.125Z",
});
const timestamp = 1755354843095; // x-actalink-timestamp
const receivedSignature =
"5f4215dab9e5328236064278f147d3b6a9e55f3355ae33e200e596f96537adb2"; // x-actalink-signature
const secret = "..."; // from Acta HUB dashboard
const result = verifyWebhookSignature({
rawBody,
timestamp,
receivedSignature,
secret,
});
if (result) {
console.log("✅ Signature is valid");
} else {
console.log("❌ Signature is invalid");
}
Webhook Event Types
Acta Deposit supports the following webhook event types:
single.deposit.executed
{
"id": "48ae8926-997e-4a8f-8424-c9c2e2b59263",
"eventType": "single.deposit.executed",
"eventData": {
"toJSON": {
"data": {
"id": "2ae22f8b-7f86-4241-8718-89f0ce8617cb",
"token": {
"name": "USD Coin",
"amount": "120000",
"symbol": "USDC",
"address": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"chainId": 137,
"fiatISO": "USD",
"logoURI": "https://api.acta.link/deposit/v1/logos/usdc.png",
"decimals": 6,
"logoSourceURI": "https://api.acta.link/deposit/v1/logos/usdc.png"
},
"status": "EXECUTED",
"chainId": 137,
"network": {
"name": "Polygon",
"chainId": 137
},
"currency": "USDC",
"transaction": {
"id": "2ae22f8b-7f86-4241-8718-89f0ce8617cb",
"fee": "194285",
"hash": "0xfc726ae601de19485a0d4b88ffd374edfd73dd789d2f2d96afe500b36ac4df73",
"orgId": "f6df600c-eb64-428c-bf58-4b969522572b",
"amount": "120000",
"status": "EXECUTED",
"chainId": 137,
"projectId": "dcb827a2-8b5e-48af-9271-05c47b231555",
"executedAt": "2025-07-27T15:08:46.457Z",
"executedBy": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB",
"paymentType": "single",
"feeInclusive": true,
"tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"senderAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB",
"receiverAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
"recurringDepositTransactionId": null
},
"feeInclusive": true,
"tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"senderAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB",
"receiverAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70"
},
"type": "single",
"projectId": "dcb827a2-8b5e-48af-9271-05c47b231555",
"organisationId": "f6df600c-eb64-428c-bf58-4b969522572b"
}
},
"createdAt": "2025-07-27T15:08:46.466Z"
}
recurring.deposit.executed
{
"id": "2a054aa3-d74a-4d7f-b49a-72a7f27ed1b2",
"eventType": "recurring.deposit.executed",
"eventData": {
"toJSON": {
"data": {
"id": "c9f5d972-2c30-4eb6-9790-666277ef5544",
"token": {
"name": "USD Coin",
"amount": "3000000",
"symbol": "USDC",
"address": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"chainId": 137,
"fiatISO": "USD",
"logoURI": "https://api.acta.link/deposit/v1/logos/usdc.png",
"decimals": 6,
"logoSourceURI": "https://api.acta.link/deposit/v1/logos/usdc.png"
},
"status": "EXECUTED",
"chainId": 137,
"network": {
"name": "Polygon",
"chainId": 137
},
"currency": "USDC",
"startedAt": "2025-07-27T15:46:00.000Z",
"transaction": {
"id": "c9f5d972-2c30-4eb6-9790-666277ef5544",
"fee": "921717",
"hash": "0x4e8d188b3ad1e1739773a00286665f862dfb6be33206f6a5bbbdfdcaf7a3fc14",
"orgId": "f6df600c-eb64-428c-bf58-4b969522572b",
"amount": "3000000",
"status": "EXECUTED",
"chainId": 137,
"projectId": "dcb827a2-8b5e-48af-9271-05c47b231555",
"executedAt": "2025-07-27T15:47:03.367Z",
"executedBy": "0xFF01d8625923C382c4e6fb1307749f10c48908AF",
"paymentType": "RECURRING",
"feeInclusive": true,
"tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"senderAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
"receiverAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB",
"recurringDepositTransactionId": "d130d138-cfa3-4dbb-acc5-291a1e4b79ea"
},
"feeInclusive": true,
"intervalUnit": "5mins",
"tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"intervalCount": 3,
"senderAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
"nextExecutionAt": "2025-07-27T15:46:00.000Z",
"receiverAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB",
"effectedInterval": 0,
"latestIntervalCount": 0
},
"type": "recurring",
"projectId": "dcb827a2-8b5e-48af-9271-05c47b231555",
"organisationId": "f6df600c-eb64-428c-bf58-4b969522572b"
}
},
"createdAt": "2025-07-27T15:47:08.924Z"
}
recurring.deposit.failed
{
"id": "e91848ca-cb47-46f3-86f1-86b47010d5e0",
"eventType": "recurring.deposit.failed",
"eventData": {
"toJSON": {
"data": {
"id": "d130d138-cfa3-4dbb-acc5-291a1e4b79ea",
"token": {
"name": "USD Coin",
"amount": "3000000",
"symbol": "USDC",
"address": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"chainId": 137,
"fiatISO": "USD",
"logoURI": "https://api.acta.link/deposit/v1/logos/usdc.png",
"decimals": 6,
"logoSourceURI": "https://api.acta.link/deposit/v1/logos/usdc.png"
},
"reason": "BALANCE",
"status": "PARTIAL",
"chainId": 137,
"network": {
"name": "Polygon",
"chainId": 137
},
"attempts": 3,
"currency": "USDC",
"startedAt": "2025-07-27T15:46:00.000Z",
"feeInclusive": true,
"intervalUnit": "5mins",
"tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"intervalCount": 3,
"senderAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
"nextExecutionAt": "2025-07-27T16:05:01.240Z",
"receiverAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB",
"effectedInterval": 3,
"latestIntervalCount": 3
},
"type": "recurring",
"projectId": "dcb827a2-8b5e-48af-9271-05c47b231555",
"organisationId": "f6df600c-eb64-428c-bf58-4b969522572b"
}
},
"createdAt": "2025-07-27T16:24:01.467Z"
}
recurring.deposit.scheduled
{
"id": "0b0c8a03-23da-4ca5-a202-48da0ca5e91e",
"eventType": "recurring.deposit.scheduled",
"eventData": {
"toJSON": {
"data": {
"id": "d130d138-cfa3-4dbb-acc5-291a1e4b79ea",
"token": {
"name": "USD Coin",
"amount": "3000000",
"symbol": "USDC",
"address": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"chainId": 137,
"fiatISO": "USD",
"logoURI": "https://api.acta.link/deposit/v1/logos/usdc.png",
"decimals": 6,
"logoSourceURI": "https://api.acta.link/deposit/v1/logos/usdc.png"
},
"status": "ONGOING",
"chainId": 137,
"network": {
"name": "Polygon",
"chainId": 137
},
"currency": "USDC",
"startedAt": "2025-07-27T15:46:00.000Z",
"transaction": null,
"feeInclusive": true,
"intervalUnit": "5mins",
"tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"intervalCount": 3,
"senderAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
"nextExecutionAt": "2025-07-27T15:46:00.000Z",
"receiverAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB",
"latestIntervalCount": 0
},
"type": "recurring",
"projectId": "dcb827a2-8b5e-48af-9271-05c47b231555",
"organisationId": "f6df600c-eb64-428c-bf58-4b969522572b"
}
},
"createdAt": "2025-07-27T15:41:44.764Z"
}
recurring.deposit.completed
{
"id": "59020745-29d2-4011-a352-2c25e020b6fa",
"eventType": "recurring.deposit.completed",
"eventData": {
"toJSON": {
"data": {
"id": "cc5864a8-a0b5-44ad-968a-6a7eeb4eb641",
"token": {
"name": "USD Coin",
"amount": "1000000",
"symbol": "USDC",
"address": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"chainId": 137,
"fiatISO": "USD",
"logoURI": "https://api.acta.link/deposit/v1/logos/usdc.png",
"decimals": 6,
"logoSourceURI": "https://api.acta.link/deposit/v1/logos/usdc.png"
},
"status": "EXECUTED",
"chainId": 137,
"network": {
"name": "Polygon",
"chainId": 137
},
"currency": "USDC",
"startedAt": "2025-07-28T09:16:00.000Z",
"transaction": {
"id": "cc5864a8-a0b5-44ad-968a-6a7eeb4eb641",
"fee": "921717",
"hash": "0x9b7d3cea1db614f6151162f7fa128e3b266a85d72858de68a8894e6a70bd094f",
"orgId": "f6df600c-eb64-428c-bf58-4b969522572b",
"amount": "1000000",
"status": "EXECUTED",
"chainId": 137,
"projectId": "dcb827a2-8b5e-48af-9271-05c47b231555",
"executedAt": "2025-07-28T09:16:02.796Z",
"executedBy": "0xFF01d8625923C382c4e6fb1307749f10c48908AF",
"paymentType": "RECURRING",
"feeInclusive": true,
"tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"senderAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
"receiverAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB",
"recurringDepositTransactionId": "16cf2316-83a8-44b2-af23-94126e5812d1"
},
"feeInclusive": true,
"intervalUnit": "5mins",
"tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"intervalCount": 1,
"senderAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
"nextExecutionAt": "2025-07-28T09:16:00.000Z",
"receiverAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB",
"effectedInterval": 0,
"latestIntervalCount": 0
},
"type": "recurring",
"projectId": "dcb827a2-8b5e-48af-9271-05c47b231555",
"organisationId": "f6df600c-eb64-428c-bf58-4b969522572b"
}
},
"createdAt": "2025-07-28T09:16:08.324Z"
}
recurring.deposit.due
{
"id": "825a9ee4-74b1-48e4-a028-c58aa79c16f7",
"eventType": "recurring.deposit.due",
"eventData": {
"toJSON": {
"data": {
"id": "d130d138-cfa3-4dbb-acc5-291a1e4b79ea",
"token": {
"name": "USD Coin",
"amount": "3000000",
"symbol": "USDC",
"address": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"chainId": 137,
"fiatISO": "USD",
"logoURI": "https://api.acta.link/deposit/v1/logos/usdc.png",
"decimals": 6,
"logoSourceURI": "https://api.acta.link/deposit/v1/logos/usdc.png"
},
"reason": "BALANCE",
"status": "DUE",
"chainId": 137,
"network": {
"name": "Polygon",
"chainId": 137
},
"attempts": 1,
"currency": "USDC",
"feeInclusive": true,
"intervalUnit": "5mins",
"tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"intervalCount": 3,
"senderAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
"nextExecutionAt": "2025-07-27T16:12:01.183Z",
"receiverAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB",
"effectedInterval": 3,
"expectedExecutionAt": "2025-07-27T15:59:02.445Z",
"latestIntervalCount": 3
},
"type": "recurring",
"projectId": "dcb827a2-8b5e-48af-9271-05c47b231555",
"organisationId": "f6df600c-eb64-428c-bf58-4b969522572b"
}
},
"createdAt": "2025-07-27T16:07:01.240Z"
}
Best Practices for Webhooks
✅ Always verify the webhook signature to avoid spoofed requests.
✅ Respond quickly with a 200 OK
— don’t wait for downstream logic to finish.
✅ Handle retries properly. If Acta doesn't receive a 2xx
status, we retry using exponential backoff.
Retry Strategy
- Max Attempts: 10
- Initial Delay: 30 seconds
- Backoff Strategy: Exponential (doubles every attempt)
- No Delay Cap: Keeps doubling until final attempt
- Total Retry Window: ~4h 16m
Retry schedule
Attempt | Delay (s) | Delay (min) | Cumulative Time |
---|---|---|---|
1st | 30 | 0.5 | 0:00:30 |
2nd | 60 | 1 | 0:01:30 |
3rd | 120 | 2 | 0:03:30 |
4th | 240 | 4 | 0:07:30 |
5th | 480 | 8 | 0:15:30 |
6th | 960 | 16 | 0:31:30 |
7th | 1920 | 32 | 1:03:30 |
8th | 3840 | 64 | 2:07:30 |
9th | 7680 | 128 | 4:15:30 |
10th | 15360 | 256 | 8:31:30 |
🔁 If all retries fail, the webhook is marked as unsuccessful.