Skip to main content

Webhook Endpoint & Deliveries

Set up the endpoint TensorRail delivers webhooks to and rotate its signing secret in the dashboard; read that configuration and inspect or retry deliveries through the API.

New accounts receive no webhooks until an endpoint is configured

A new account has no webhook endpoint configured. Events still occur (payments succeed, refunds resolve), but nothing is delivered anywhere until you set a URL. Configuring your endpoint is the first integration step after your first test payment.

1. Configure your endpoint​

In the dashboard, go to Developers → Webhooks, set your endpoint URL, and reveal your signing secret.

Requirements for the URL:

  • Public HTTPS only. Plain http://, private/internal addresses, and localhost are rejected (webhook_url must be a public https endpoint).
  • It must answer 2xx within 30 seconds (the delivery timeout), though you should respond in well under a second and do any heavy work asynchronously. See delivery and retries.

Changing the endpoint and rotating the secret are both dashboard operator actions, under Developers → Webhooks, requiring a signed-in session with fresh two-factor verification; there is no API call for either, under any credential, so a leaked key can never redirect your webhook traffic or swap your signing secret. The rotated secret is shown once. Every new event and every automatic retry is signed at send time with your current secret, so from the moment you rotate they all use the new one; there is no window in which the old secret still applies. A manual retry re-sends an event exactly as it was first signed, so a manual retry of an event from before the rotation fails verification against the new secret. Deploy the new secret to your verifier before you rotate.

Stopping delivery​

To stop delivery, clear the endpoint URL: Developers → Webhooks, empty the field, and save. The dashboard confirms delivery has stopped, and GET /v1/webhooks/config then reads back webhook_url: null.

Nothing is lost while delivery is stopped: TensorRail still records every event, so GET /v1/webhooks/deliveries keeps listing them and each one can be retried once you set an endpoint again. Delivery stays stopped until you do.

2. Read your configuration​

GET /v1/webhooks/config: scope webhooks:read. Works with your API key.

curl "https://api.tensorrail.com/v1/webhooks/config" \
-H "Authorization: Bearer rail_full_live_xxx"

The response contains your profile's webhook_details (including webhook_url) plus a payment_response_hash_key_set boolean that tells you whether a signing secret is configured. The signing secret's value is never returned by any read endpoint; it is shown only when (re)generated in the dashboard.

webhook_url is null when no endpoint is configured, either because you have not set one yet, or because you stopped delivery.

3. Inspect deliveries​

GET /v1/webhooks/deliveries: scope webhooks:read

Lists recent webhook events for your account. Query parameters: limit (default 50, max 100) and event_type (e.g. payment_succeeded). History covers up to 90 days.

event_type takes one event name from the event types table, which lists every event your endpoint can receive. A name the platform does not recognise is rejected with 400 and ERR_2002.

curl "https://api.tensorrail.com/v1/webhooks/deliveries?limit=20&event_type=payment_succeeded" \
-H "Authorization: Bearer rail_full_live_xxx"

GET /v1/webhooks/deliveries/{event_id}/attempts: scope webhooks:read

Returns the full attempt chain for one event. This /v1 surface is gateway-built, not generated from the engine's schema, so the fields are documented here, not in the reference. Each entry carries the event's own fields, event_id, event_type, event_class, object_id, is_delivery_successful, initial_attempt_id and created, and three more that describe the delivery itself:

FieldWhat it holds
delivery_attemptinitial_attempt, automatic_retry or manual_retry. Absent on older records.
requestWhat we sent: headers and body.
responseWhat your endpoint answered: headers, body and status_code.

delivery_attempt is the field to read when you want to know why a delivery happened: automatic_retry is a step of the retry schedule, manual_retry is someone pressing Retry. It can be absent on records written before it existed, and in that case the older rule still holds: an entry whose initial_attempt_id equals its own event_id is the first delivery, and every other entry in the chain is a retry of it. Order by created to read the chain in sequence, and use is_delivery_successful to find the attempt that landed.

request.body is the payload as it was sent to you, with a few platform-internal fields removed: client_secret, sdk_authorization, split_payments and connector are stripped from this view. Everything your integration reads is present. Verify signatures against the live webhook, not against this record.

The list endpoint above returns { "events": [...], "total_count": <n> }; total_count is the total matching your filter, not the number in this page, so it is what you paginate against.

curl "https://api.tensorrail.com/v1/webhooks/deliveries/evt_1a2b3c/attempts" \
-H "Authorization: Bearer rail_full_live_xxx"

4. Retry a delivery​

POST /v1/webhooks/deliveries/{event_id}/retry: scope webhooks:write. Works with your API key.

Manually re-sends one event to your endpoint, useful after fixing a bug on your side or after the automatic retry schedule (16 retries over about 24 hours) was exhausted.

The dashboard button is test-mode only

Developers → Webhooks shows a Retry control, and it is disabled in live mode. That is deliberate, not a fault: re-driving a live delivery is a live-key action, and the dashboard session is view-only against live data. In live mode, use this endpoint with your live secret key. ::: The re-delivery is signed like any other and appears in the attempt chain as a further entry sharing the original's initial_attempt_id.

curl -X POST "https://api.tensorrail.com/v1/webhooks/deliveries/evt_1a2b3c/retry" \
-H "Authorization: Bearer rail_full_live_xxx"

Remember that your handler must be idempotent: a manual retry of an already-processed event will arrive with the same event_id.

Endpoint summary​

On the API, with your rail_* key:

EndpointScope
GET /v1/webhooks/configwebhooks:read
GET /v1/webhooks/deliverieswebhooks:read
GET /v1/webhooks/deliveries/{event_id}/attemptswebhooks:read
POST /v1/webhooks/deliveries/{event_id}/retrywebhooks:write

In the dashboard only, under Developers → Webhooks, with a fresh two-factor step-up; there is no API call for either:

ActionWhere
Change the endpoint (or clear it to stop delivery)Developers → Webhooks: edit the URL field and click Save (or clear it and click Stop delivery)
Rotate the signing secretDevelopers → Webhooks: click Rotate