Skip to Content
DevelopersWebhooks

Webhooks

Webhooks send a message to your own system whenever something happens in your AI Twin app, such as a new subscription, a failed payment or a new user. Your system can then act on it right away.

For example, you can:

  • Add new subscribers to your CRM or email list
  • Send a win-back email when a subscription is cancelled
  • Alert your support team when a user leaves negative feedback
  • Offer an upgrade when a user reaches their message limit

Requirements: Webhooks are turned on per workspace. Contact support@steno.ai to enable them.


Turning On Webhooks

You need a web address (URL) on your side that can receive the messages. Your developer or automation tool provides this.

  1. In the dashboard, open Webhooks.
  2. Select the app you want events from, then click Add endpoint.
  3. Enter your receiving URL. It must start with https://.
  4. Choose the events you want to receive.
  5. Copy the signing secret and give it to your developer. It is used to confirm that messages really come from Steno.

Each app has its own endpoints. If you have more than one app, add an endpoint for each.


Events We Send

Click an event to see an example of its data.

Subscriptions

All subscription events include source (web or mobile), store (app_store, play_store, or null for web purchases) and tier (the plan key, for example pro).

subscription.started: a user starts a paid subscription or trial

{ "source": "web", "store": null, "status": "trialing", "tier": "pro", "effectiveAt": "2026-09-08T09:14:03.000Z" }

status is trialing or active.

subscription.renewed: a subscription renews for a new billing period

{ "source": "mobile", "store": "app_store", "tier": "pro", "periodStartsAt": "2026-09-08T09:14:03.000Z", "periodEndsAt": "2026-10-08T09:14:03.000Z", "isTrialConversion": false }

isTrialConversion and isResubscription are included when they apply.

subscription.cancellation_scheduled: a user cancels, and access ends at a future date

{ "source": "mobile", "store": "app_store", "tier": "pro", "reason": "user_request", "accessEndsAt": "2026-10-08T09:14:03.000Z" }

reason is one of user_request, payment_failed, billing_error or unknown.

subscription.expired: a subscription has ended and access is gone

{ "source": "mobile", "store": "app_store", "tier": "pro", "reason": "billing_error" }

reason is one of user_request, payment_failed, billing_error or unknown.

subscription.billing_issue: a subscription payment failed

{ "source": "web", "store": null, "tier": "pro", "reason": "payment_failed", "attempt": 2, "nextRetryAt": "2026-09-10T09:14:03.000Z" }

attempt and nextRetryAt can be null when the payment provider does not report them.

subscription.plan_changed: a user moves to a different plan

{ "source": "mobile", "store": "app_store", "previousTier": "basic", "newTier": "pro" }

Users

user.created: a new user signs up

{ "source": "web" }

source is web or mobile.

user.deleted: a user deletes their account

{ "source": "web" }

source is web or mobile.

Feedback

feedback.submitted: a user rates or reports an AI Twin response

{ "source": "web", "category": "negative" }

category is one of positive, negative, report, harmful, untrue or unhelpful.

Usage

These events fire for users on a free plan with a usage limit.

quota.message_exhausted: a user reaches their message limit

{ "used": 50, "limit": 50, "unit": "messages", "window": "rolling-24h", "resetsAt": "2026-09-09T09:14:03.000Z" }

A user who keeps sending after reaching the limit can trigger more than one of these events.

quota.voice_exhausted: a user reaches their voice call limit

{ "used": 3600, "limit": 3600, "unit": "seconds", "window": "rolling-24h", "resetsAt": "2026-09-09T09:14:03.000Z" }

A user who keeps calling after reaching the limit can trigger more than one of these events.

Testing

webhook.ping: you send a test event from the dashboard

{}

data is empty. Use it to check that your endpoint is reachable.

Payload Format

Every event is an HTTPS POST with a JSON body in the same format. The event details above go in its data field.

Show fields

{ "id": "evt_608838dd1d7a1ef0b9f28d0c99410ace504b8af02433d57d25bd4466b352c78a", "type": "subscription.started", "version": 1, "isTest": false, "occurredAt": "2026-09-08T09:14:03.000Z", "app": { "slug": "acme-coach" }, "user": { "email": "user@example.com" }, "data": { "source": "mobile", "store": "app_store", "status": "active", "tier": "pro", "effectiveAt": "2026-09-08T09:14:03.000Z" } }
FieldDescription
idUnique event ID. Stays the same if the event is delivered more than once.
typeThe event type, for example subscription.started
versionPayload format version, currently 1
isTesttrue for test events sent from the dashboard
occurredAtWhen the event happened, in UTC
app.slugWhich of your apps the event belongs to
user.emailThe user’s email address. Can be null for some mobile purchases.
dataEvent details, shown above

Receiving a Webhook

Your system should reply quickly to say it got the message, then do any slow work afterwards.

Details

  • Respond with any 2xx status code to confirm you received the event.
  • Respond within 15 seconds. A slower response counts as a failure.
  • Save the event and respond first, then do any slow work afterwards.
  • Any other response, including a redirect, counts as a failure and is retried.

Security

Every message is signed with your secret, so your system can check that it really came from Steno.

How signing works

Every webhook is signed with your endpoint’s signing secret, so you can confirm it came from Steno and was not changed.

Each request carries a message ID, a timestamp and a signature in its headers. The signature is an HMAC-SHA256 of the message ID, the timestamp and the raw request body, using your signing secret. Retries of the same event reuse the same message ID. Your developer should:

  1. Verify the signature against the raw, unparsed request body before using the event.
  2. Reject requests whose timestamp is more than 5 minutes old or in the future.
  3. Reject any request that fails either check with a non-2xx status.

We share the exact header names and sample verification code for your language during onboarding.

Rotating the secret

Open the endpoint, go to Settings, and click Rotate next to the signing secret. You can keep the old secret working for 24 hours (recommended) or 7 days while you deploy the new one, or stop it immediately. During that time, requests are signed with both secrets.

Keep the signing secret private. Store it on your server, never in a website or mobile app.


Delivery and Retries

If your system misses a message, we try again for a little over a day, and you can resend missed messages from the dashboard.

Retry schedule

If your endpoint does not respond with a 2xx in time, we retry the event. After the first attempt, we retry after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours and 10 hours again. After the last retry, the event is marked as failed.

If every delivery to an endpoint keeps failing for 5 days, the endpoint is turned off. Turn it back on with the live switch on its Settings tab.

Duplicates and order

An event can arrive more than once, for example when your endpoint times out after processing it. The id stays the same across deliveries, so store the IDs you have processed and skip repeats.

Events can arrive in a different order from the order they happened. If order matters, use occurredAt instead of arrival order.

Sending missed events again

If your endpoint was down, open the endpoint and use Send missed events again. Choose how far back to go (the last hour, the last 24 hours, or a time you pick), and whether to resend only failed events or everything in that window.

To resend a single event, open it from the Deliveries tab and click Resend.


Testing

You can send a test event and see every delivery in the dashboard.

Details

  • Send a test event. Open the endpoint and click Send test event on the Overview tab. You can send an example of any event type. Test events have isTest: true.
  • Check deliveries. The Deliveries tab lists every delivery with its result. Open one to see the request we sent, your endpoint’s response, and each attempt.

Need Help?

We help every customer set up webhooks. Email support@steno.ai and we will walk your developer through receiving and verifying events.

Last updated on