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
- Receive the webhook
POSTrequest - Read the
Signatureheader value - Hash the request body using your Secret Key with the
generateWebhookSignaturefunction above - Compare your computed hash with the
Signatureheader value
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
| Status | Description |
|---|---|
processing | Withdrawal initiated and/or transaction received by blockchain (sent twice — see below) |
done | Withdrawal completed on both platform and blockchain side |
rejected | Withdrawal 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
| Field | Type | Description |
|---|---|---|
type | string | Always "withdrawal" |
status | string | processing, done, or rejected |
asset | string | Withdrawn asset (e.g., usdt) |
dchain | string | Asset + network (e.g., trc20usdt) |
amount | number | Withdrawn amount |
fee | string | Withdrawal fee |
to | string | Receiver wallet address |
txHash | string | Blockchain transaction link (empty for processing #1 and rejected) |
uid | string | Internal 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
| Status | Description |
|---|---|
processing | Webhook for initiated deposit — transaction info is received by the system from blockchain |
done | Deposit completed on system's side |
failed / frozen | Deposit 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
| Field | Description |
|---|---|
amount | Deposited amount |
asset | Asset name |
network | Network name |
depositAddress | Address that received the deposit |
status | processing, done, failed, or frozen |
id | Internal address ID |
internal_user_id | Internal ID of the address owner |
hash | Blockscan transaction ID |
email | Address owner's user email |
feePayer | Who pays the fee: buyer or merchant |
The email parameter won't be received within the webhook if the deposit address has a null value for user's email parameter.