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
| Event | Meaning |
|---|---|
landed | Earliest signal — the network has seen your tx in a shred. Pre-processing: not yet executed. ~100ms after broadcast. |
processed | The cluster has executed your tx. Default for new webhooks. Sub-second under typical conditions. |
confirmed | Supermajority commitment. Reached ~one block after processed. |
finalized | Rooted and irreversible. Reached ~12-13s after processed. |
failed | Your tx errored on chain. Terminal — mutually exclusive with the commitment events for the same signature. |
Subscribe to any subset on create. The event taxonomy matches the /v1/stream/tx SSE stream, so a webhook + SSE consumer of the same signature see consistent state.
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
POST /v1/webhooks
Content-Type: application/json
X-API-Key: vk_...Body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Public 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. |
events | string[] | No | Subset of event types to subscribe to. Defaults to ["processed"]. |
Example
{
"url": "https://my-app.example.com/webhooks/venum",
"events": ["processed", "finalized"]
}Response 201
{
"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
| Status | Description |
|---|---|
400 | Missing/malformed url, unknown event type, or no dbApiKeyId (env-keyed callers cannot subscribe). |
401 | Missing or invalid API key. |
409 | Webhook limit reached (20 per key). |
GET /v1/webhooks
List every webhook on the calling key. Secrets are not returned.
Response 200
{
"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.
{ "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.
{ "deleted": true }404 if the webhook isn't found or isn't owned by the calling key.
Event payload
Each delivery POSTs a JSON body matching the subscribed event type:
POST /your/webhook/path
Content-Type: application/json
X-Venum-Signature: t=1715900000000,v1=4b7f...{
"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
| Field | Type | Description |
|---|---|---|
type | string | The event type — "landed" / "processed" / "confirmed" / "finalized" / "failed". |
signature | string | Base58 transaction signature. Use this as the idempotency key on your side. |
slot | number | Slot number where the event was observed. |
inputMint / outputMint | string | Token mint addresses. |
inputAmount / outputAmount | string | Raw token amounts (lamports / token base units), as strings to preserve precision. |
dex | string | null | Venue routed through (e.g. "orca", "raydium-clmm", "meteora-dlmm"). |
poolAddress | string | null | Pool routed through. For multi-hop routes, comma-joined. |
walletAddress | string | The user's wallet (signer of the swap). |
quoteId | string | The 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):
{
"type": "failed",
"signature": "5K...",
"slot": 254812345,
"err": "InstructionError: [3, { Custom: 6004 }]",
...
}Signature header
Every webhook delivery includes an X-Venum-Signature header:
X-Venum-Signature: t=<unix_ms>,v1=<hex>| Component | Description |
|---|---|
t | Unix milliseconds when the payload was signed. |
v1 | Lowercase 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
signatureas 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:
| Tier | Limit |
|---|---|
| free | 30 / min |
| starter | 120 / min |
| pro | 300 / min |
