Skip to content

Webhooks

Subscribe to signed HTTP callbacks for the lifecycle of every transaction you submit through /v1/swap (and, going forward, /v1/send).

All webhook endpoints require a self-service API key (vk_...). Manage keys at app.venum.dev.

Event types

EventMeaning
landedEarliest signal — the network has seen your tx in a shred. Pre-processing: not yet executed. ~100ms after broadcast.
processedThe cluster has executed your tx. Default for new webhooks. Sub-second under typical conditions.
confirmedSupermajority commitment. Reached ~one block after processed.
finalizedRooted and irreversible. Reached ~12-13s after processed.
failedYour tx errored on chain. Terminal — mutually exclusive with the commitment events for the same signature.
address-txAn on-chain transaction involves a registered address. See POST /v1/webhooks/:id/track-address. Different payload shape from the commitment events.

The commitment-tier events form the transaction webhook product — subscribe to them, then register signatures with /track-tx or auto-fire by submitting through /v1/swap/submit. The event taxonomy matches the /v1/stream/tx SSE stream so a webhook + SSE consumer of the same signature see consistent state.

address-tx is the address webhook product — subscribe to it alone, then register addresses with /track-address. Use a separate webhook row from the commitment events; don't mix the two on the same row.

POST /v1/webhooks

Create a webhook subscription. The webhook secret is returned exactly once in this response — capture it now or rotate by deleting and re-creating.

Request

http
POST /v1/webhooks
Content-Type: application/json
X-API-Key: vk_...

Body

FieldTypeRequiredDescription
urlstringYesPublic HTTPS URL where events will be POSTed. Localhost / private network hostnames are rejected — for local dev, expose your handler via an ngrok or cloudflared tunnel. Max 512 chars.
eventsstring[]NoSubset of event types to subscribe to. Defaults to ["processed"]. This array also determines the webhook's kind — see below.

Kind is implicit in events

There is no kind field. The webhook's kind is inferred from the event types:

  • events contains any of landed / processed / confirmed / finalized / failedTransaction webhook. Pair with POST /v1/webhooks/:id/track-tx or auto-fire from /v1/swap/submit.
  • events is ["address-tx"]Address webhook. Pair with POST /v1/webhooks/:id/track-address.

Mixing both in one webhook is technically accepted but discouraged — your handler then has to branch on two payload shapes. Create one webhook per kind.

Example

json
{
  "url": "https://my-app.example.com/webhooks/venum",
  "events": ["processed", "finalized"]
}

Response 201

json
{
  "id": 42,
  "url": "https://my-app.example.com/webhooks/venum",
  "events": ["processed", "finalized"],
  "createdAt": "2026-05-17T20:00:00.000Z",
  "pausedAt": null,
  "secret": "whsec_3f9d1c8a2b7e0f5d6a4c8b2e9f1d3a7c5b8e0f2d4a6c8b1e"
}

WARNING

The secret is shown once. Store it now — there is no endpoint to retrieve it later.

Errors

StatusDescription
400Missing/malformed url, unknown event type, or no dbApiKeyId (env-keyed callers cannot subscribe).
401Missing or invalid API key.
409Webhook limit reached (20 per key).

GET /v1/webhooks

List every webhook on the calling key. Secrets are not returned.

Response 200

json
{
  "webhooks": [
    {
      "id": 42,
      "url": "https://my-app.example.com/webhooks/venum",
      "events": ["processed", "finalized"],
      "createdAt": "2026-05-17T20:00:00.000Z",
      "pausedAt": null
    }
  ]
}

PATCH /v1/webhooks/:id

Pause or resume a webhook. Paused webhooks remain in the list but do not receive events until resumed.

json
{ "paused": true }

Response 200: returns the updated webhook (same shape as GET).

DELETE /v1/webhooks/:id

Permanently delete a webhook. The associated secret becomes unusable.

json
{ "deleted": true }

404 if the webhook isn't found or isn't owned by the calling key.

POST /v1/webhooks/:id/test

Fire a synthetic event at the registered URL so you can verify your endpoint, HMAC validation, retry behaviour, and infrastructure end-to-end without waiting for real swap traffic.

The dispatch path is identical to production: same dispatch fan-out, same HMAC, same 3× exponential retry. Payload values are placeholders (prefixed test-) and include test: true so the receiver can branch.

Body

FieldTypeRequiredDescription
eventTypestringNoWhich event type to fire — must be one your webhook subscribes to. Defaults to landed.

Response 202

json
{
  "dispatched": true,
  "event": "landed",
  "webhookId": 42,
  "url": "https://my-app.example.com/webhooks/venum",
  "testSignature": "test-1748707391000-abc12345"
}

409 if the webhook is paused. 404 if not found / not owned.

POST /v1/webhooks/:id/track-tx

Register an arbitrary on-chain transaction signature for tracking. Venum polls getSignatureStatuses and fires the matching commitment events at the webhook URL as the tx walks through its lifecycle.

Use this for any tx — submitted through Venum's swap surface or broadcast elsewhere. Tracker is bounded to 60 seconds; auto-stops at the highest requested commitment.

Body

FieldTypeRequiredDescription
signaturestringYesBase58 transaction signature (~88 chars).
eventsstring[]NoSubset of processed / confirmed / finalized to fire. Default: all three. landed requires ShredStream and is rejected on this endpoint — use the swap-flow auto-fire for shred-tier signal.

Response 202

json
{
  "tracking": true,
  "signature": "5KxabcDef...3nF",
  "events": ["processed", "confirmed", "finalized"],
  "webhookId": 42,
  "url": "https://my-app.example.com/webhooks/venum",
  "budgetMs": 60000
}

Errors

StatusDescription
400Invalid signature or unsupported event type.
404Webhook not found or not owned.
409Webhook is paused.
503Server RPC connection unavailable.

POST /v1/webhooks/:id/track-address

Register a Solana address against an address-tx-subscribed webhook. Venum polls getSignaturesForAddress every ~400 ms and fires for every new tx involving the address. End-to-end chain → webhook p50 latency is ~600 ms at the confirmed commitment level.

The webhook must subscribe to address-tx events on create (and only those — don't mix kinds).

Body

FieldTypeRequiredDescription
addressstringYesBase58 Solana pubkey (32–44 chars).
labelstringNoYour identifier for the address (≤ 64 chars). Returned verbatim in every event.
commitment"confirmed" | "finalized"NoCommitment level for the poll. Defaults to confirmed. See note below.

commitment field

Per-subscription. Different addresses on the same webhook can run at different commitment levels.

ValueLatency p50Use case
confirmed (default)~600 msFastest available signal. Near-zero re-org risk in practice.
finalized~13 sTx is irreversible — the right choice for audit/accounting webhooks that must never fire on a re-org'd tx.

processed is not accepted on this endpoint. Solana's RPC method getSignaturesForAddress only exposes confirmed/finalized; processed-tier signal will arrive when Venum moves address watching to WS push (logsSubscribe / blockSubscribe).

Response 201

json
{
  "id": 1,
  "webhookId": 42,
  "address": "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE",
  "label": "Treasury",
  "commitment": "confirmed",
  "createdAt": "2026-05-31T17:13:11.526Z",
  "note": "First poll establishes the cursor at the current newest signature without firing events. Subsequent activity fires `address-tx` webhooks."
}

The first poll after create sets the cursor at the current newest signature for the address without firing events. Subsequent polls fire for every new signature. There is no historical backfill; if you need recent activity, call getSignaturesForAddress yourself.

Errors

StatusDescription
400Invalid address (not base58 32–44 chars) or trivial address (System Program etc.) — see the guide for the rejected list.
400Webhook does not subscribe to address-tx.
402Tier does not support address tracking (anonymous keys).
404Webhook not found or not owned.
409Address already tracked by this webhook, or tier address-cap reached.
503Platform-wide address-subscription cap reached.

Tier limits

TierMax addresses per webhookDelivery cap
free1100 deliveries/hour, then events dropped
starter5Unlimited
pro20Unlimited

GET /v1/webhooks/:id/addresses

List active address subscriptions for the webhook.

Response 200

json
{
  "addresses": [
    {
      "id": 1,
      "address": "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE",
      "label": "Treasury",
      "lastSeenSignature": "5xY9z…",
      "commitment": "confirmed",
      "createdAt": "2026-05-31T17:13:11.526Z"
    }
  ]
}

lastSeenSignature is null before the first poll; afterwards it tracks the most recent signature the poller has seen for that address.

DELETE /v1/webhooks/:id/addresses/:trackingId

Stop tracking an address. Soft-delete — the row is preserved with deleted_at set for audit, but the poller no longer fires for it.

json
{ "deleted": true }

404 if not found or not owned.

Event payload

Each delivery POSTs a JSON body matching the subscribed event type:

http
POST /your/webhook/path
Content-Type: application/json
X-Venum-Signature: t=1715900000000,v1=4b7f...
json
{
  "type": "processed",
  "signature": "5KxabcDef...3nF",
  "slot": 254812345,
  "inputMint": "So11111111111111111111111111111111111111112",
  "outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
  "inputAmount": "1000000000",
  "outputAmount": "143820000",
  "dex": "orca",
  "poolAddress": "Hp53XEt...UnxVtA",
  "walletAddress": "9zT...kqW",
  "quoteId": "q_abcd1234..."
}

Common fields

FieldTypeDescription
typestringThe event type — "landed" / "processed" / "confirmed" / "finalized" / "failed".
signaturestringBase58 transaction signature. Use this as the idempotency key on your side.
slotnumberSlot number where the event was observed.
inputMint / outputMintstringToken mint addresses.
inputAmount / outputAmountstringRaw token amounts (lamports / token base units), as strings to preserve precision.
dexstring | nullVenue routed through (e.g. "orca", "raydium-clmm", "meteora-dlmm").
poolAddressstring | nullPool routed through. For multi-hop routes, comma-joined.
walletAddressstringThe user's wallet (signer of the swap).
quoteIdstringThe quoteId returned by /v1/swap/build.

failed event

When type === "failed", the payload includes an err field with the on-chain error and no outputAmount (the tx didn't execute):

json
{
  "type": "failed",
  "signature": "5K...",
  "slot": 254812345,
  "err": "InstructionError: [3, { Custom: 6004 }]",
  ...
}

address-tx event

Different shape from the commitment-tier payloads — no swap-specific fields, but includes the matched address and your label:

json
{
  "type": "address-tx",
  "signature": "5KxabcDef...3nF",
  "slot": 332419471,
  "address": "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE",
  "label": "Treasury",
  "commitment": "confirmed",
  "blockTime": 1748707391,
  "err": null
}
FieldTypeDescription
typestringAlways "address-tx".
signaturestringBase58 signature of the tx involving the address.
slotnumberSlot the tx landed in.
addressstringWhich of your registered addresses matched.
labelstring | nullYour label for the address (verbatim).
commitmentstring"confirmed" or "finalized" — whichever level this subscription was created at.
blockTimenumber | nullUnix seconds, may be null for very fresh blocks.
errstring | nullOn-chain error if the tx failed (non-null = failed tx that still touched the address).

Call getTransaction(signature) if you need full tx details — accounts touched, instructions, balance changes, logs. The webhook payload is intentionally lightweight to keep delivery fast.

Signature header

Every webhook delivery includes an X-Venum-Signature header:

X-Venum-Signature: t=<unix_ms>,v1=<hex>
ComponentDescription
tUnix milliseconds when the payload was signed.
v1Lowercase hex of HMAC_SHA256(secret, "<t>.<rawBody>").

Reject deliveries where t is older than your tolerance window (5 minutes recommended) before comparing v1. See the Webhooks guide for verification examples in Node and Python.

Delivery semantics

  • At-least-once. Use the transaction signature as your idempotency key. The same (signature, type) pair will not be delivered more than once to any given webhook within the same process lifetime.
  • Retries. Failed deliveries retry up to 3 times with exponential backoff (~0.5s, ~1s, ~2s). Non-2xx responses count as failure.
  • Timeout. Each delivery attempt times out after 5 seconds.

Rate limits

Webhook management endpoints share the dashboard rate-limit bucket:

TierLimit
free30 / min
starter120 / min
pro300 / min