Billing/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: "subscription.billing.due",
  eventData: {
    data: {
      id: "sub_jeRBLFCOuK2jE8Q1209xnPh5g3ELmfnP",
      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: "subscription",
    paylink: {
      id: "paylink_xsOVX8sNubCCpPSsmMlmy7llRAkhHyvM",
      name: "Hooli Nucleus Subscription",
      status: "active",
      paylinkType: "subscription",
    },
    plan: {
      id: "prod_jy7Z3JdTuTf1uBqhFFl7x23AdoPJjIRX",
      name: "Hooi Nucleus",
      price: {
        id: "price_kBN95XmlS6kUCNJu98ZpAoc4WT6UX3A2",
        price: "0.15",
        currency: "USDC",
        currencyType: "crypto",
        intervalUnit: "5mins",
        intervalCount: 2
  },
  status: "ACTIVE",
  imageUrl: "",
  description: "Monthly subscription for Hooli Nucleus",
  paymentType: "recurring"
}
    organisationId: "SwHuo5b9UCc43wFCphlC0uQCkQQVvcga",
  },
  createdAt: "2025-08-16T14:22:02.125Z",
});
 
const timestamp = 1755354122183; // x-actalink-timestamp
const receivedSignature =
  "e5d8cf0d6cd3294adec97c1eaa2cfa42a5d3e6a9f421530d231bdf57a56222af"; // 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.billing.executed

{
  "id": "1f3d9b55-2930-4cbc-a96e-210f25a64944",
  "eventType": "single.billing.executed",
  "eventData": {
    "data": {
      "id": "txn_E8v3z08dxUtyZFivjbAidJbdDS1pcrvz",
      "token": {
        "name": "USD Coin",
        "amount": "0xf4240",
        "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": "txn_E8v3z08dxUtyZFivjbAidJbdDS1pcrvz",
        "fee": "0x2f6ed",
        "hash": "0x66961cffada0290a4872ee42e92e7faca4bf30493cbc589786fbefe544e0c95e",
        "amount": "0xf4240",
        "status": "EXECUTED",
        "chainId": 137,
        "paylinkId": "paylink_V2iUszhF1qH1KhEn1wQB2lkx9boHnqeh",
        "executedAt": "2025-08-16T14:03:47.833Z",
        "executedBy": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
        "paymentType": "one-time",
        "feeInclusive": false,
        "tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
        "senderAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
        "organizationId": "SwHuo5b9UCc43wFCphlC0uQCkQQVvcga",
        "receiverAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB"
      },
      "feeInclusive": false,
      "tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
      "senderAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
      "receiverAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB"
    },
    "type": "one-time",
    "paylink": {
      "id": "paylink_V2iUszhF1qH1KhEn1wQB2lkx9boHnqeh",
      "name": "Hooli Music One time",
      "status": "active",
      "paylinkType": "one-time"
    },
    "plan": {
      "id": "prod_jy7Z3JdTuTf1uBqhFFl7x23AdoPJjIRX",
      "name": "Hooi Nucleus",
      "price": {
        "id": "price_kBN95XmlS6kUCNJu98ZpAoc4WT6UX3A2",
        "price": "0.15",
        "currency": "USDC",
        "currencyType": "crypto",
        "intervalUnit": "5mins",
        "intervalCount": 2
  },
  "status": "ACTIVE",
  "imageUrl": "",
  "description": "Monthly subscription for Hooli Nucleus",
  "paymentType": "recurring"
}
    "organisationId": "SwHuo5b9UCc43wFCphlC0uQCkQQVvcga"
  },
  "createdAt": "2025-08-16T14:03:47.832Z"
}

subscription.billing.executed

{
  "id": "8694f943-753a-433c-b28e-4b3576682079",
  "eventType": "subscription.billing.executed",
  "eventData": {
    "data": {
      "id": "sub_PPBseoxbK9p5fMTe3SttfoYIBeDAHii7",
      "token": {
        "name": "USD Coin",
        "amount": "0x1adb0",
        "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-08-16T14:04:40.000Z",
      "transaction": {
        "id": "txn_aYkHIR0b36sRiJTccCU8wSqI6ku4jAJw",
        "fee": "0xe1075",
        "hash": "0xfe8e7c5e60c8834ecc7e1ef38e34dd169c5ec17c8f136d61b20f1ddf2ae2c90a",
        "amount": "0x1adb0",
        "status": "EXECUTED",
        "chainId": 137,
        "paylinkId": "paylink_vJGyzXpjuQb7Z7zXpcuIwVvjkkFFhMmI",
        "executedAt": "2025-08-16T14:05:06.311Z",
        "executedBy": "0xFF01d8625923C382c4e6fb1307749f10c48908AF",
        "paymentType": "subscription",
        "feeInclusive": false,
        "tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
        "senderAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
        "organizationId": "SwHuo5b9UCc43wFCphlC0uQCkQQVvcga",
        "receiverAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB"
      },
      "feeInclusive": false,
      "intervalUnit": "5mins",
      "tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
      "intervalCount": 2,
      "senderAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
      "nextExecutionAt": "2025-08-16T14:10:06.305Z",
      "receiverAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB",
      "effectedInterval": 1,
      "latestIntervalCount": 1
    },
    "type": "subscription",
    "paylink": {
      "id": "paylink_vJGyzXpjuQb7Z7zXpcuIwVvjkkFFhMmI",
      "name": "Hooli Music Subscription",
      "status": "active",
      "paylinkType": "subscription"
    },
    "plan": {
      "id": "prod_jy7Z3JdTuTf1uBqhFFl7x23AdoPJjIRX",
      "name": "Hooi Nucleus",
      "price": {
        "id": "price_kBN95XmlS6kUCNJu98ZpAoc4WT6UX3A2",
        "price": "0.15",
        "currency": "USDC",
        "currencyType": "crypto",
        "intervalUnit": "5mins",
        "intervalCount": 2
      }
    },
    "organisationId": "SwHuo5b9UCc43wFCphlC0uQCkQQVvcga"
  },
  "createdAt": "2025-08-16T14:05:11.956Z"
}

subscription.billing.failed

{
  "id": "e91848ca-cb47-46f3-86f1-86b47010d5e0",
  "eventType": "subscription.billing.failed",
  "eventData": {
    "toJSON": {
      "data": {
        "id": "sub_jeRBLFCOuK2jE8Q1209xnPh5g3ELmfnP",
        "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": "FAILED",
        "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": "subscription",
      "paylink": {
        "id": "paylink_xsOVX8sNubCCpPSsmMlmy7llRAkhHyvM",
        "name": "Hooli Nucleus Subscription",
        "status": "active",
        "paylinkType": "subscription"
      },
      "plan": {
        "id": "prod_jy7Z3JdTuTf1uBqhFFl7x23AdoPJjIRX",
        "name": "Hooi Nucleus",
        "price": {
          "id": "price_kBN95XmlS6kUCNJu98ZpAoc4WT6UX3A2",
          "price": "0.15",
          "currency": "USDC",
          "currencyType": "crypto",
          "intervalUnit": "5mins",
          "intervalCount": 2
        }
      },
      "organisationId": "f6df600c-eb64-428c-bf58-4b969522572b"
    }
  },
  "createdAt": "2025-07-27T16:24:01.467Z"
}

subscription.billing.scheduled

{
  "id": "0fccb912-d1f0-4936-86ca-fdfb0b72c6ab",
  "eventType": "subscription.billing.scheduled",
  "eventData": {
    "data": {
      "id": "sub_PPBseoxbK9p5fMTe3SttfoYIBeDAHii7",
      "token": {
        "name": "USD Coin",
        "amount": "0x1adb0",
        "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-08-16T14:04:40.000Z",
      "transaction": null,
      "feeInclusive": false,
      "intervalUnit": "5mins",
      "tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
      "intervalCount": 2,
      "senderAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
      "nextExecutionAt": "2025-08-16T14:04:40.000Z",
      "receiverAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB",
      "latestIntervalCount": 0
    },
    "type": "subscription",
    "paylink": {
      "id": "paylink_vJGyzXpjuQb7Z7zXpcuIwVvjkkFFhMmI",
      "name": "Hooli Music Subscription",
      "status": "active",
      "paylinkType": "subscription"
    },
    "plan": {
      "id": "prod_jy7Z3JdTuTf1uBqhFFl7x23AdoPJjIRX",
      "name": "Hooi Nucleus",
      "price": {
        "id": "price_kBN95XmlS6kUCNJu98ZpAoc4WT6UX3A2",
        "price": "0.15",
        "currency": "USDC",
        "currencyType": "crypto",
        "intervalUnit": "5mins",
        "intervalCount": 2
      }
    },
    "organisationId": "SwHuo5b9UCc43wFCphlC0uQCkQQVvcga"
  },
  "createdAt": "2025-08-16T14:04:50.137Z"
}

subscription.billing.completed

{
  "id": "337288e2-40de-4e4e-b855-f725eca89e92",
  "eventType": "subscription.billing.completed",
  "eventData": {
    "data": {
      "id": "sub_PPBseoxbK9p5fMTe3SttfoYIBeDAHii7",
      "token": {
        "name": "USD Coin",
        "amount": "0x1adb0",
        "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": "COMPLETED",
      "chainId": 137,
      "network": {
        "name": "Polygon",
        "chainId": 137
      },
      "currency": "USDC",
      "startedAt": "2025-08-16T14:04:40.000Z",
      "transaction": {
        "id": "txn_8fRiLX0pEtJ1B1Dx0jnun1hfPDqZcYpW",
        "fee": "0x4aaa8",
        "hash": "0x80618cd40f89283948d681adbfac8890fd647c4ca9b241a1ac188443056d138f",
        "amount": "0x1adb0",
        "status": "EXECUTED",
        "chainId": 137,
        "paylinkId": "paylink_vJGyzXpjuQb7Z7zXpcuIwVvjkkFFhMmI",
        "executedAt": "2025-08-16T14:11:03.854Z",
        "executedBy": "0xFF01d8625923C382c4e6fb1307749f10c48908AF",
        "paymentType": "subscription",
        "feeInclusive": false,
        "tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
        "senderAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
        "organizationId": "SwHuo5b9UCc43wFCphlC0uQCkQQVvcga",
        "receiverAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB"
      },
      "feeInclusive": false,
      "intervalUnit": "5mins",
      "tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
      "intervalCount": 2,
      "senderAddress": "0x061BA68bc8208F4AddBeE86F74F17D77129cCF70",
      "nextExecutionAt": "2025-08-16T14:16:03.847Z",
      "receiverAddress": "0xEBFa37194fA74bA3e8195446948FC3B9c72E08AB",
      "effectedInterval": 2,
      "latestIntervalCount": 2
    },
    "type": "subscription",
    "paylink": {
      "id": "paylink_vJGyzXpjuQb7Z7zXpcuIwVvjkkFFhMmI",
      "name": "Hooli Music Subscription",
      "status": "active",
      "paylinkType": "subscription"
    },
    "plan": {
      "id": "prod_jy7Z3JdTuTf1uBqhFFl7x23AdoPJjIRX",
      "name": "Hooi Nucleus",
      "price": {
        "id": "price_kBN95XmlS6kUCNJu98ZpAoc4WT6UX3A2",
        "price": "0.15",
        "currency": "USDC",
        "currencyType": "crypto",
        "intervalUnit": "5mins",
        "intervalCount": 2
      }
    },
    "organisationId": "SwHuo5b9UCc43wFCphlC0uQCkQQVvcga"
  },
  "createdAt": "2025-08-16T14:11:09.755Z"
}

subscription.billing.due

{
  "id": "c837a151-a962-44e0-b3e3-b4f61743d7bb",
  "eventType": "subscription.billing.due",
  "eventData": {
    "data": {
      "id": "sub_jeRBLFCOuK2jE8Q1209xnPh5g3ELmfnP",
      "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": "subscription",
    "paylink": {
      "id": "paylink_xsOVX8sNubCCpPSsmMlmy7llRAkhHyvM",
      "name": "Hooli Nucleus Subscription",
      "status": "active",
      "paylinkType": "subscription"
    },
    "plan": {
      "id": "prod_jy7Z3JdTuTf1uBqhFFl7x23AdoPJjIRX",
      "name": "Hooi Nucleus",
      "price": {
        "id": "price_kBN95XmlS6kUCNJu98ZpAoc4WT6UX3A2",
        "price": "0.15",
        "currency": "USDC",
        "currencyType": "crypto",
        "intervalUnit": "5mins",
        "intervalCount": 2
      }
    },
    "organisationId": "SwHuo5b9UCc43wFCphlC0uQCkQQVvcga"
  },
  "createdAt": "2025-08-16T14:22:02.125Z"
}

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.