Skip to content
Last updated

Notification channels

Events describe what happened on-chain; channels decide where the alert goes. A subscription selects matching activity, waits for its trigger and sends a notification through its assigned channel. Two kinds are available, and they share one lifecycle.

Webhook + Telegram Signed deliveries One lifecycle Notification history

Use a project key with api:channel:write to create, update and test channels, api:channel:delete to delete them and api:channel:read to read channels and history. Keep a separate Idempotency-Key and the original inputs for each mutation; reuse them after a lost response. See Idempotency.

Pick a channel

A webhook posts JSON to your backend for automated processing; a Telegram channel sends a formatted message to a chat for humans to read. Production setups usually run both - one to act, one to watch.

webhookfor machines

An HTTP POST with the full WebhookV2Payload, signed so you can trust it.

Delivery

HTTP POST to your endpoint

Format

JSON (WebhookV2Payload)

Best for

Automated systems and backends

Setup

URL + optional custom headers

Sample delivery
POST/webhooks/vilna
X-Webhook-Signaturet=1793059200,v1=…
{ event_id, item, references }
200 OK 142 ms
telegramfor humans

A formatted bot message in a group, channel or DM - readable at a glance.

Delivery

Bot message to a chat

Format

Formatted text

Best for

Human monitoring and alerting

Setup

Bot token + chat_id

Sample alert
Vilnabot
Deposit confirmed: +1,500 USDT at 0x8ba1…c400
12:48

Webhook channels

A webhook channel sends an HTTP POST with a JSON payload to your endpoint when an attached subscription's trigger is met. Create one with POST /channels.

Choose kind: webhook, set the receiver URL and any dedicated headers. Select payload_version: v2 for structured events; webhook payload versions are chosen independently of the HTTP API version. The create-channel page above contains request and response schemas and language examples.

Headers are stored and returned unmasked to authorized channel readers. If your receiver needs authentication, use a dedicated receiver credential and restrict channel read access. Never put a Vilna API key there. Verify the webhook signature as well.

Store the webhook secret

Save webhook_secret from the creation response in a secrets manager. Retrying that creation with the same key and body can recover it for 60 seconds from completion, under current access and only while the secret remains current. Retries do not extend the window; a later GET returns metadata, not plaintext. Recover or rotate deliberately if the secret was lost. Use it to verify X-Webhook-Signature; your receiver’s credentials and Telegram bot token are separate from the Vilna API key.

Relay it anywhere

A webhook is just an HTTP POST, so one channel can fan out to wherever your architecture needs it. Point the URL at a thin proxy and forward from there:

Your webhookPOST /webhooks/vilnaone signed payload per event
Database

write the raw event

Message queue

SQS, RabbitMQ, Pub/Sub

Slack / Discord

human-readable alerts

PagerDuty

escalate high-value events

Custom backend

any HTTP endpoint

For a specific integration, contact support@vilna.io.

Telegram channels

A Telegram channel sends formatted messages to a chat through the Bot API. Set the bot up first, then create the channel:

Create a bot with @BotFather and save the bot token.

Add the bot to the target chat - a group, channel or direct message.

Get the chat ID with @userinfobot or the Telegram getUpdates API.

Use Create channel with kind: telegram, the bot token and chat_id. Choose the message language; set thread_id for a forum topic, or leave it at 0. The operation page contains the configuration schema and request examples.

One lifecycle, every channel

Both kinds follow the same five-step lifecycle. Only the config differs - the endpoints are identical.

01Create

Name it and set the config: Create channel.

POST /channels
02Test

Push a sample payload: Test channel.

POST /channels/{id}/actions/test
03Use

With api:subscription:write, create a subscription for this channel. It selects which transactions trigger notifications.

delivering
04Update

Rename or repoint the config: Update channel.

PATCH /channels/{id}
05Delete

Permanently remove it: Delete channel.

DELETE /channels/{id}

When a channel switches itself off

max_attempts (1-10) bounds the automatic delivery attempts for one notification, counting the first. Failed attempts are retried on a fixed schedule with a growing delay; ten attempts stretch over about a day. When the budget runs out, the channel is disabled with disabled_reason: attempts_exhausted. A destination the service has confirmed as unusable disables the channel at once with channel_error. There is no switch to keep a channel on regardless; the attempt budget is the knob.

Disabling a channel, automatically or by hand, cancels every notification still waiting on it: they end with status: canceled and reason: channel_disabled. Re-enabling admits only new transactions, not the cancelled backlog. A manual resend and a test never count against the budget.

Raise the budget with PATCH /channels/{channel_id}. Raising it, or repairing the destination, does not enable a disabled channel by itself; set is_enabled explicitly, in the same request.

To detect a disabled channel, poll GET /channels and watch for is_enabled: false, or list GET /notifications with status=canceled and the channel's id to see what was dropped and why.

Editing a webhook's URL or headers, or a Telegram chat, cancels the automatic retries of deliveries that already made at least one attempt; rotating the webhook signing secret does not.

Protecting a payment flow

A disabled channel does not lose a deposit. The transaction, the activity rows and the balance are recorded whether or not a notification went out; what is cancelled is the delivery. Three habits keep the gap small:

Set max_attempts: 10 on channels used for payment monitoring. That is about a day of automatic retries before the channel gives up.

Create a second subscription with the same filters on a second channel, such as a Telegram chat or another webhook. A subscription is bound to one channel, so disabling one leaves the other delivering.

After re-enabling, reconcile dropped notifications and movements during the outage.

Reconciliation also requires api:activity:read:

  1. Get the chain_gid and txid of dropped notices from GET /notifications?status=canceled&channel_id=…. Collect every page of GET /activity with date_from and date_to for the outage window.
  2. Resend notices whose payload is still retained; see resend responses. On notification.payload_unavailable, reconcile from the transaction instead. On notification.send_in_progress, wait for the queued or running attempt.
  3. Group all activity rows by chain_gid, txid (hash with payload_version: v1), address and asset_gid, and aggregate signed delta.base values across incoming and outgoing effects. Several Bitcoin outputs to one address belong to one group. Apply your own deposit eligibility and confirmation rules before crediting; gross incoming value alone is not a credit rule. Save the group key and accounting result atomically.

event_id identifies a notification, not a transfer: another subscription can report the same deposit under a different event_id. Use it to deduplicate deliveries of one notification, and the shared credit key to reconcile across subscriptions and API polling.

Testing a channel

After creation, use Test channel. Select only a transaction family, such as evm; you cannot choose a transaction, recipient or event kind. Use the operation page for the request schema and examples.

The call answers 202 with a notification_id. A test works on a disabled channel and never changes its state; it sends one built-in sample for the given family, not live data, in a single attempt with no automatic retries. A test that has not gone out within five minutes ends with reason test_expired. Inspect its notification before deliberately starting a new test with a new intent key; retrying the old key only replays its accepted result.

Notification history

Every notification and each delivery attempt behind it is kept as history. Use it to confirm a test landed and to debug failures. List notifications from GET /notifications, newest first, and open GET /notifications/{notification_id}/logs to see every attempt for one of them.

Start with channel_id and status; separate tests with is_test and bound the window with date_from and date_to. A txid filter also requires chain_gid. Read every result page. The notification-list operation above defines all filters, response schemas and request examples.

Each attempt in the log carries the HTTP status and error your endpoint returned. When alerts stop arriving, query with status=failed and read the last attempt.

Once the endpoint is fixed, use POST /notifications/{notification_id}/actions/resend. Supply a separate Idempotency-Key for a fresh manual resend; retry the same key after a lost response.

ResponseNext action
202The resend is queued with the same body and event_id; deduplicate the delivery by event_id.
429 notification.resend_delayedWait for Retry-After, covering the delivery pause or cooldown.
409 notification.send_in_progressWait for the existing attempt.
409 notification.payload_unavailableReconcile the transaction instead.

A resend uses the channel’s current destination, headers and signing credentials. The retained body does not preserve the old channel configuration or update transaction facts. Read the invoice again before treating a resend as completion. The payload is kept for 30 days after a notification finishes, then is unavailable for resend.

Best practices

Follow the Webhooks reference for signature verification, deduplication and response deadlines. Use separate channels per environment and purpose, keeping critical alerts apart from routine notifications.