Webhooks

Speend sends a POST to url_callback when a status changes. Which status changes actually trigger a webhook differs between payments and payouts — see below.

Where the delivery address comes from

Which address wins — the one from the merchant dashboard or the one from the request body — depends on the method:

Method Priority
Static wallet (createStaticWallet) The address from the merchant dashboard — url_callback from the request is ignored
Invoice (createPayment) The address from the request wins over the one from the merchant dashboard
Payout (payout/create) The address from the merchant dashboard — the same as for a static wallet

Webhook — invoice and static wallet

It is sent on many meaningful transitions, intermediate statuses included (not just the final ones) — on the move to check, on a partial underpayment, on an AML release and so on.

{
  "type": 2,
  "uuid": "019f69ef-f130-7064-b822-09b39a40b99c",
  "amount": "2",
  "paid_amount": "6.170554",
  "paid_amount_usd": "2.00210147",
  "merchant_amount": "6.07799569",
  "commission_percent": "1.5",
  "commission_fixed": "0",
  "commission": "0.09255431",
  "is_final": true,
  "status": "paid",
  "network": "TRON",
  "currency": "TRX",
  "txid": "36540b5842d27b23da70415439281e2...",
  "from": "TQn9Y2*****LVLTiPKX",
  "to": "TCEUYh*****bNhPMK6W",
  "order_id": "1002",
  "additional_data": null,
  "sign": "91be60615455288c41ee177ab8b02b19"
}

A static wallet additionally carries the wallet_address_uuid and wallet_id fields. When auto-conversion is enabled for the merchant, a convert object is present as well (with the same structure as in payment/info, see the "Payment details" section).

type — what produced the notification (payments and static wallet)

type Source
1 Static wallet
2 Invoice (createPayment)

Webhook — payouts (Payout)

Unlike payments, a payout webhook is not sent on every status change but only for a limited set of statuses — see the table below. Intermediate technical statuses never leave the system.

{
  "uuid": "019f89aa-bfe9-7308-8e40-fb5b1345a5a4",
  "type": 1,
  "status": "success",
  "is_final": true,
  "amount": "7",
  "commission": "0",
  "network": "SOL",
  "currency": "USDT",
  "address": "8Q3zsm*****tVUzasKQ",
  "txid": "DgR9Tg*****AFe5nA9j6zJ6",
  "order_id": "13",
  "description": null,
  "comment": null,
  "created_at": "2026-07-22T11:51:36+00:00",
  "updated_at": "2026-07-22T11:52:04+00:00",
  "sign": "c8980a53111b04abea70c2371ff2fca7"
}

type — how the payout was created (Payout only)

type How the payout was created
1 Through the public API (payout/create)
2 By hand in the merchant dashboard
3 By a Speend administrator

The statuses a webhook is sent for (Payout)

Status in the API (getStatus) Status in the webhook
ON_QUEUE process
SUCCESS success
CANCELED cancel
FAILED fail
AML_FAILED aml_fail

The webhook arrives for these 5 statuses only, no matter how the payout was created. REQUESTED, AWAITING_CONFIRMATION and the internal stages (ON_QUEUE_CONVERSION, ON_NETWORK_QUEUE) are never sent out.

The sign signature

Every webhook carries a sign field.
The signature lets you make sure the webhook really came from Speend and was not forged by a third party. Verifying it is mandatory before you trust the status from a webhook.

type — what produced the notification (payments and static wallet)

// webhook_password is optional
signKey = api_key + webhook_password

// the body — without the sign field, keys in the order of the original JSON
sign = MD5(
  base64( json_encode(body, JSON_UNESCAPED_UNICODE) ) . signKey
)

Which key to use

The key for the formula depends on the event, not directly on the type field. Payout and Static wallet share the same type value (1), but the keys differ:

Event Key (api_key in the formula)
Payout (sending funds out) The payout key (api_key_withdraw)
Static wallet (top-up) The main key (api_key_main)
Invoice (createPayment) The main key (api_key_main)
speend