Skip to main content

Webhooks

PaidLys sends webhook notifications via POST requests to your configured endpoint. Webhooks are signed with a Signature Token for security verification.

You can get your Signature Token on the Settings → Tokens page.

Setup

Configure your webhook URL either through the dashboard (Settings → Tokens → Webhook URL) or via API:

curl -X PUT "https://my.paidlys.com/api/company/settings/webhook-url" \
-H "Authorization: Bearer YOUR_PUBLIC_TOKEN" \
-H "Content-Type: application/json" \
-d '<body>'

Body:

{
"webHookUrl": "https://your-server.com/webhook"
}

Signature Verification

Every webhook contains a Signature header generated using HMAC-SHA512 with your Secret Key. Always verify this signature to ensure the webhook is authentic.

How PaidLys sends webhooks

private async sendWebhookNotification(
webhookUrl: string,
payload: any,
secretKey: string,
): Promise<void> {
const signature = this.generateWebhookSignature(secretKey, payload);
await axios.post(webhookUrl, payload, {
headers: {
'Content-Type': 'application/json',
signature: signature,
},
});
}

private generateWebhookSignature(secretKey: string, payload: any): string {
const hmac = createHmac('sha512', secretKey.trim());
hmac.update(JSON.stringify(payload));
return hmac.digest('hex');
}

Where secretKey is the merchant's Secret Key from Settings → Tokens page.

How to verify on your side

  1. Receive the webhook POST request
  2. Read the Signature header value
  3. Hash the request body using your Secret Key with the generateWebhookSignature function above
  4. Compare your computed hash with the Signature header value
Security

We recommend verifying every webhook. For each request, hash the payload using your Secret Key and compare it with the Signature header. This prevents attackers from sending fake webhooks.

Invoice Webhooks

All invoice webhooks have the following structure:

{
"invoiceId": "%invoice_id%",
"status": "%status_name%",
"message": "%user_readable_description%"
}

Created

Invoice has been created.

{
"invoiceId": "96850db7-41dd-4ce7-bacd-10371f96100a",
"status": "created",
"message": "Invoice created"
}

Pending

Invoice has been opened by a client, filled with client's email, and payment currency is selected. Payment address is provided to the client. No deposits have been received yet.

{
"invoiceId": "96850db7-41dd-4ce7-bacd-10371f96100a",
"status": "pending",
"message": "User started payment process, but hasn't paid yet"
}

Processing

Payment initiation information from blockchain has been received. Invoice is waiting for transaction to be completed.

{
"invoiceId": "96850db7-41dd-4ce7-bacd-10371f96100a",
"status": "processing",
"message": "The client has made a payment, transaction confirmation is pending"
}

Done

Invoice payment has been successfully received. The amount is equal to or greater than the expected invoice amount.

{
"invoiceId": "96850db7-41dd-4ce7-bacd-10371f96100a",
"status": "done",
"message": "Invoice paid"
}

Wrong

Transaction has been received, but the amount is lower than expected.

{
"invoiceId": "96850db7-41dd-4ce7-bacd-10371f96100a",
"status": "wrong",
"message": "The payment amount is lower than the amount stated on the invoice"
}

Refunded

A previously "Wrong"-status invoice has been declined by the merchant. Funds are returned to the client.

{
"invoiceId": "96850db7-41dd-4ce7-bacd-10371f96100a",
"status": "refunded",
"message": "Invoice refunded"
}

Closed

Invoice has expired. Either a "Pending" invoice wasn't paid in time, or a "Processing" invoice didn't receive payment within the required time.

{
"invoiceId": "96850db7-41dd-4ce7-bacd-10371f96100a",
"status": "closed",
"message": "Invoice closed"
}

Withdrawal Webhooks

Withdrawal webhooks have a different payload structure with transaction details.

Statuses

StatusDescription
processingWithdrawal initiated and/or transaction received by blockchain (sent twice — see below)
doneWithdrawal completed on both platform and blockchain side
rejectedWithdrawal blocked by AML/TMS checks and/or failed on blockchain side

Processing (webhook #1)

Sent when the withdrawal is initiated. txHash is empty at this stage.

{
"type": "withdrawal",
"status": "processing",
"asset": "usdt",
"dchain": "trc20usdt",
"amount": 5,
"fee": "3.0",
"to": "TNyG6gPkBsF8S8Sy6deirNCVVydVFTFK7c",
"txHash": "",
"uid": "156-77704488"
}

Processing (webhook #2)

Sent when the transaction is received by the blockchain. txHash is now available.

{
"type": "withdrawal",
"status": "processing",
"asset": "usdt",
"dchain": "trc20usdt",
"amount": 5,
"fee": "3.0",
"to": "TNyG6gPkBsF8S8Sy6deirNCVVydVFTFK7c",
"txHash": "https://tronscan.org/#/transaction/...",
"uid": "156-77704488"
}

Done

Withdrawal completed successfully. txHash contains the full blockchain transaction link.

{
"type": "withdrawal",
"status": "done",
"asset": "usdt",
"dchain": "trc20usdt",
"amount": 5,
"fee": "3.0",
"to": "TNyG6gPkBsF8S8Sy6deirNCVVydVFTFK7c",
"txHash": "https://tronscan.org/#/transaction/1e4cc555a36ec5cc4bac0c54050b12d0b0c202baa3b952522b88cd32384104ae",
"uid": "156-77704488"
}

Rejected

Withdrawal has been blocked by AML/TMS checks or failed on the blockchain side. No txHash is provided.

{
"type": "withdrawal",
"status": "rejected",
"asset": "usdt",
"dchain": "trc20usdt",
"amount": 5,
"fee": "3.0",
"to": "TNyG6gPkBsF8S8Sy6deirNCVVydVFTFK7c",
"uid": "156-77704488"
}

Withdrawal Payload Fields

FieldTypeDescription
typestringAlways "withdrawal"
statusstringprocessing, done, or rejected
assetstringWithdrawn asset (e.g., usdt)
dchainstringAsset + network (e.g., trc20usdt)
amountnumberWithdrawn amount
feestringWithdrawal fee
tostringReceiver wallet address
txHashstringBlockchain transaction link (empty for processing #1 and rejected)
uidstringInternal UUID from the withdrawal API response

Static Address Deposit Webhooks

Deposits that are made for static deposit addresses trigger webhooks with the following payload.

Statuses

StatusDescription
processingWebhook for initiated deposit — transaction info is received by the system from blockchain
doneDeposit completed on system's side
failed / frozenDeposit has been blocked by AML/TMS checks and/or failed on the blockchain side

Payload

{
"amount": "%deposited_amount%",
"asset": "%asset_name%",
"network": "%network_name%",
"depositAddress": "%address_value%",
"status": "%processing/done/failed/frozen%",
"id": "%internal_address_id%",
"internal_user_id": "%internal_id_of_address_owner%",
"hash": "%blocscan_transaction_id%",
"email": "%address_owner_user_email%",
"feePayer": "%buyer/merchant%"
}

Payload Fields

FieldDescription
amountDeposited amount
assetAsset name
networkNetwork name
depositAddressAddress that received the deposit
statusprocessing, done, failed, or frozen
idInternal address ID
internal_user_idInternal ID of the address owner
hashBlockscan transaction ID
emailAddress owner's user email
feePayerWho pays the fee: buyer or merchant
note

The email parameter won't be received within the webhook if the deposit address has a null value for user's email parameter.