Skip to main content
Receive trade push notifications using webhooks. A webhook subscription specifies an event type you wish to listen to, optional filters, and the URL that receives matching callbacks. The management API is available at https://data.gravia.trade/v1/webhooks. The current live feed covers confirmed OrderFilled events from chain (A single trade might be split into multiple different fills).
This is made to be closely compatible with Struct to function as a drop in replacement with minimal changes required.

Available Events

All the events support ongoing subscriptions and one_shot subscriptions, which are deleted after their first successful delivery.

Quickstart

You need a Gravia API key and a public HTTPS endpoint that accepts JSON POST requests. Localhost and private network destinations are not supported, and redirects are not followed.
You can obtain a Gravia API key by letting us know, check out Quickstart to get up and running with us!

1. Set your API key

Send your API key in the Authorization header, without a Bearer prefix. Use this header for all webhook requests.
Subscriptions are owned and grouped by the API key that was used to create them. A webhook ID is not a credential and is only availed to manage it, you still need to transmit the Authorization header on all requests.

2. Create a subscription

Subscriptions optionally support HMAC signing on the webhook notification that’s being forwarded to you, to avail this, you can provide the signing secret when initially creating the webhook. Replace the receiver URL and signing secret below. Use a separate, randomly generated signing secret for callback verification; it isn’t your Gravia API key.
A successful creation returns HTTP 201. Save the subscription ID from data.id and keep your signing secret in your receiver’s secret storage. Subscriptions are active immediately after a success response. Management success responses wrap the result in data:
Here, data: {} illustrates the response wrapper. Create and read operations return the subscription object, including id, url, event, filters when present, status, description, has_secret, created_at, updated_at, and credits_used_24h. Ordinary reads do not return the signing secret.
Credits consumed field doesn’t imply that we bill you for the usage, as of writing this, the Gravia API is free to use upon obtaining an API Key.

3. Send a test callback

Set the ID returned by the create request:
The test sends a synthetic callback to your configured URL using your signing secret, if set. Its event is trader_new_trade_test or close_to_bond_test. Inspect data.success, data.status_code, data.error, and data.duration_ms in the management response to see the delivery result. Tests send out one event and work regardless of subscription’s active status, doesn’t consume a one-shot subscription or change latch/threshold states for close_to_bond events. These can be used to verify receiver connectivity, the data provided is dummy data. Essentially, test events don’t in any capacity effect your ongoing subscriptions and can be triggered as needed.

Configure new-trade notifications

A trader_new_trade subscription receives each trade satisfying its filters. For example, this create-request body tracks one wallet’s trades worth at least $100, with prices from 20 to 80 cents:
Replace the example wallet with the address you want to follow. Prices range from 0 to 1, so 0.80 means 80 cents. When both price bounds are present, min_price must be less than or equal to max_price. Omitting filters or supplying {} subscribes to all supported new trades.

Configure price-threshold notifications

For close_to_bond, price filters define alternative high and low trigger zones. They do not define a price range. Set at least one threshold. Thresholds must be between 0 and 1 inclusive. If both are set, min_price must be greater than max_price. Actual trades priced exactly 0 or 1 neither trigger notifications nor rearm crossing state.

Every qualifying trade

The following create-request body sends a notification for every trade at or above 90 cents or at or below 10 cents:
Omitting trigger_mode also selects this behavior. Repeated trades inside a trigger zone each qualify.

One notification per crossing

Use crossing mode to suppress repeated notifications until the price moves far enough away from the threshold:
With these settings, a high-side notification fires at 90 cents or above. That side can fire again after a trade at 80 cents or below rearms it. A low-side notification fires at 10 cents or below and rearms at 20 cents or above. Rearming alone does not send a notification. Reaching the opposite configured trigger zone fires immediately, even without an intermediate reset trade. Crossing state is independent for each subscription and position_id. The first observed qualifying trade fires even if the price is already inside a trigger zone. Price always refers to the traded outcome token: a Down token at 95 cents matches the high side. reset_distance is measured from the configured threshold, not the latest peak or trough. 0.10 means 10 cents, not a relative 10% change. It must be greater than 0 and less than 1. Configured reset boundaries must also stay strictly inside the price range:
  • High side: min_price - reset_distance > 0.
  • Low side: max_price + reset_distance < 1.
Changing the event or filters, or resuming a paused subscription, starts a newly armed state. Changing only the URL, description, or secret preserves crossing state. State normally survives service restarts; by default, it expires 30 days after the last firing or side-switch event. Expiration or loss of stored state allows the next qualifying trade to fire again without an intervening reset.

Restrict the markets or outcomes

Add any of these filters alongside the price settings: For example, add "position_ids": ["12345678901234567890"] to track a particular outcome token, replacing the example ID with the token you want to follow. Without scope filters, the thresholds apply across the supported feed.
Exclude Short-term markets setting has no effect as of writing, it exists purely to maintain compatibility.

How filters combine

Different filters use AND: a trade must satisfy every configured filter. Values inside an array use OR: matching any listed value is sufficient. Empty arrays impose no restriction. Each array can be at most 500 entries beyond which an error response is returned. Wallet addresses and condition IDs match case-insensitively. Event slugs, outcome names, and trade types match exactly. Missing metadata required by a filter prevents the trade from matching until that metadata is available. The current feed does not supply short-term classification, so enabling exclude_shortterm_markets can prevent notifications from that feed. In crossing mode, scope filters apply both to triggering and rearming. State follows observed trade processing order; delayed trades can affect later crossings. It does not reconstruct a complete price history.

Receive and verify callbacks

Callbacks are HTTPS POST requests with a JSON body. The following abbreviated example shows the envelope and selected fields of a close-to-bond notification; actual data contains additional trade fields:
Both events include trade identity and execution fields such as trader, taker, position_id, trade_id, hash, block, confirmed_at, amount_usd, shares_amount, fee, side, and price, plus market metadata when available. confirmed_at uses Unix seconds, unlike the envelope timestamp. Token IDs are strings and should remain strings in your application. Unavailable market metadata may be null. New-trade payloads additionally include trader_info, exchange, trade_type, image_url, and probability, with optional builder fields. Close-to-bond payloads instead include bond_side (high or low) and threshold. These identify the matched price threshold; side separately describes the trade as Buy or Sell. Both threshold modes use the same payload shape.

Signature verification

Callback headers include X-Webhook-ID, X-Delivery-ID, X-Event-Type, and X-Attempt. If a signing secret is configured, X-Webhook-Signature contains sha256=<hex-digest>. Compute HMAC-SHA256 over the exact raw request body using the signing secret as UTF-8 bytes, then compare the complete signature in constant time. Use the secret exactly as supplied or returned; do not decode it as base64. Verify before parsing or processing the callback. Re-serializing parsed JSON changes the signed bytes. This Python helper verifies a callback using the standard library:
Pass the unmodified request bytes, the X-Webhook-Signature header, and the signing secret associated with the subscription to this function. A receiver configured to require signatures should reject missing or invalid signatures.

Acknowledgments and retries

After verification, durably record or enqueue the callback before returning a 2xx response. Process longer-running work asynchronously. Deduplicate by id with a durable, atomic check so simultaneous retries do not repeat application actions; acknowledge already accepted deliveries with 2xx. Delivery is at least once, and callbacks can arrive out of order. Non-2xx responses and transport failures retry up to 10 total attempts, with a one-second retry delay and a 30-second request timeout. Retries retain the delivery ID but can change the attempt number, timestamp, and signature. After retries are exhausted, the subscription’s current configuration is disabled. Fix the receiver, then set status to active to re-enable it. One crossing produces one logical event, but retries may produce multiple HTTP requests. With one_shot: true, the first claimed delivery is retried as needed and a successful delivery deletes the whole subscription.
Ensure that you’re always checking and activating the subscription statuses when restarting your application or reconnecting after long network failure.

Manage subscriptions

Every endpoint below requires the creating API key in Authorization. Paths are relative to https://data.gravia.trade.

Discover and list

Event discovery returns data.events. Listing returns data.webhooks and data.total. Lists support limit (default 20, maximum 100), offset (default 0), and optional status (active or paused) and event filters.

Update, pause, or resume

Use {"status":"active"} to resume. Updates are partial: omitted or null fields retain their existing values. A supplied filters object replaces the entire filter set, so include all thresholds and scope filters you want to keep. For example, to change a high-only crossing subscription to a 95-cent threshold:
That replacement removes any previous scope filters and low threshold. Omitting trigger_mode from a replacement selects every-trade mode. filters: {} removes filtering for trader_new_trade; it is invalid for close_to_bond, which still requires a threshold. An empty secret disables callback signing. Pause and delete prevent new attempts once the configuration change is observed. Already-started requests can finish. Resuming crossing subscriptions starts newly armed state.

Rotate the signing secret

Securely save data.secret and update your receiver; ordinary reads cannot retrieve it. Rotation keeps the subscription ID and management API key unchanged. Subsequent attempts use the new secret once the change is observed. Allow for requests already in progress with the previous secret during the transition.

Inspect delivery logs

Logs show terminal delivery outcomes, not each retry or synthetic test. Recent results may take time to appear. Use limit (default 10, maximum 250), optional from and to timestamps in Unix milliseconds, and pagination_key for subsequent pages. When data.pagination.has_more is true, pass data.pagination.pagination_key as the next pagination_key, URL-encoding it. The page’s total counts entries on that page; results are newest first.

Delete a subscription

The response contains data: {"deleted": true}. Deleted subscriptions and their logs are no longer accessible through management endpoints.

Troubleshooting

Management errors include success: false, a descriptive message, and data: null.