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:

HeaderDescription
x-actalink-signatureFinal HMAC-SHA256 signature of the timestamp and payload
x-actalink-timestampUnix 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

AttemptDelay (s)Delay (min)Cumulative Time
1st300.50:00:30
2nd6010:01:30
3rd12020:03:30
4th24040:07:30
5th48080:15:30
6th960160:31:30
7th1920321:03:30
8th3840642:07:30
9th76801284:15:30
10th153602568:31:30

🔁 If all retries fail, the webhook is marked as unsuccessful.