How to Monitor Stripity Stripe with Vigilmon
Stripity Stripe is the most widely used Elixir client for the Stripe API. It handles charges, subscriptions, customers, webhooks, refunds, and the full Stripe API surface — complete with retry logic and Telemetry instrumentation. When payments are involved, silent failures cost real money: a webhook that stops delivering, a subscription renewal that times out, or a Stripe API key rotation that breaks your integration.
External monitoring catches what your application and Stripe's own dashboard can't correlate in real time. This tutorial covers:
- A health endpoint that validates your Stripe API connectivity
- HTTP uptime monitoring with Vigilmon
- Heartbeat monitoring for subscription renewal and webhook workers
- Telemetry-based latency tracking
- Slack alerts and a status page
Step 1: Add Stripity Stripe to your project
# mix.exs
defp deps do
[
{:stripity_stripe, "~> 3.0"},
{:req, "~> 0.4"} # for heartbeat pings
]
end
Configure your Stripe API key:
# config/config.exs
config :stripity_stripe,
api_key: System.get_env("STRIPE_SECRET_KEY"),
httpoison_options: [timeout: 10_000, recv_timeout: 10_000]
For retry behaviour (built-in to Stripity Stripe):
config :stripity_stripe,
api_key: System.get_env("STRIPE_SECRET_KEY"),
retry_count: 3
Step 2: Build a health endpoint for Stripe connectivity
# lib/my_app_web/plugs/health_check.ex
defmodule MyAppWeb.Plugs.HealthCheck do
import Plug.Conn
require Logger
def init(opts), do: opts
def call(%Plug.Conn{request_path: "/health"} = conn, _opts) do
checks = run_checks()
status = if checks.status == "ok", do: 200, else: 503
conn
|> put_resp_content_type("application/json")
|> send_resp(status, Jason.encode!(checks))
|> halt()
end
def call(conn, _opts), do: conn
defp run_checks do
stripe = stripe_check()
overall = stripe.status == "ok"
%{
status: if(overall, do: "ok", else: "degraded"),
stripe: stripe
}
end
defp stripe_check do
start = System.monotonic_time(:millisecond)
# Retrieve the Stripe account — lightweight connectivity probe
result =
case Stripe.Account.retrieve() do
{:ok, account} ->
elapsed = System.monotonic_time(:millisecond) - start
{:ok, elapsed, account.id}
{:error, %Stripe.Error{} = err} ->
{:error, err.message}
end
case result do
{:ok, latency_ms, account_id} ->
%{status: "ok", account_id: account_id, latency_ms: latency_ms}
{:error, reason} ->
Logger.error("[Stripe] health check failed: #{reason}")
%{status: "error", reason: reason}
end
end
end
Register the plug before the router:
# lib/my_app_web/endpoint.ex
plug MyAppWeb.Plugs.HealthCheck
plug MyAppWeb.Router
Test it:
mix phx.server
curl http://localhost:4000/health | jq .
# {
# "status": "ok",
# "stripe": {
# "status": "ok",
# "account_id": "acct_1abc...",
# "latency_ms": 312
# }
# }
Step 3: Attach Telemetry for Stripe API latency
Stripity Stripe emits Telemetry events on every API call. Capture them to expose per-endpoint latency in your health response:
# lib/my_app/telemetry.ex
defmodule MyApp.Telemetry do
require Logger
def setup do
:telemetry.attach(
"stripe-request-stop",
[:stripity_stripe, :request, :stop],
&handle_stripe_event/4,
nil
)
end
def handle_stripe_event([:stripity_stripe, :request, :stop], measurements, meta, _config) do
duration_ms = System.convert_time_unit(measurements.duration, :native, :millisecond)
if duration_ms > 5_000 do
Logger.warning("[Stripe] slow request to #{meta.endpoint}: #{duration_ms}ms")
end
:telemetry.execute([:my_app, :stripe, :latency], %{ms: duration_ms}, %{endpoint: meta.endpoint})
end
end
Call MyApp.Telemetry.setup/0 from your application start:
# lib/my_app/application.ex
def start(_type, _args) do
MyApp.Telemetry.setup()
# ...
end
Step 4: Monitor the health endpoint with Vigilmon
- Sign up at vigilmon.online
- Click New Monitor → HTTP
- Enter
https://yourdomain.com/health - Set check interval: 1 minute
- Enable keyword check: body must contain
"status":"ok" - Save
The keyword check catches degraded states: if Stripe's API is returning errors your endpoint returns HTTP 200 with "status":"degraded", and Vigilmon alerts you before customers see failed checkouts.
Step 5: Heartbeat monitoring for subscription workers
Subscription renewals and webhook deliveries are the lifeblood of a SaaS payment flow. Heartbeat monitoring confirms they ran on schedule:
# lib/my_app/workers/subscription_renewal_worker.ex
defmodule MyApp.Workers.SubscriptionRenewalWorker do
use GenServer
require Logger
@interval :timer.minutes(60)
def start_link(_), do: GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
def init(state) do
schedule()
{:ok, state}
end
def handle_info(:renew, state) do
process_due_renewals()
schedule()
{:noreply, state}
end
defp process_due_renewals do
# Retrieve and process subscriptions due for renewal
case Stripe.Subscription.list(%{status: "active"}) do
{:ok, %{data: subscriptions}} ->
Logger.info("[SubscriptionWorker] processed #{length(subscriptions)} subscriptions")
ping_vigilmon()
{:error, reason} ->
Logger.error("[SubscriptionWorker] failed: #{inspect(reason)}")
end
end
defp ping_vigilmon do
url = Application.get_env(:my_app, :vigilmon)[:heartbeat_url]
if url do
case Req.get(url, receive_timeout: 5_000) do
{:ok, %{status: 200}} -> :ok
err -> Logger.warning("[SubscriptionWorker] heartbeat ping failed: #{inspect(err)}")
end
end
end
defp schedule, do: Process.send_after(self(), :renew, @interval)
end
Add it to your supervision tree:
# lib/my_app/application.ex
children = [
MyApp.Repo,
MyAppWeb.Endpoint,
MyApp.Workers.SubscriptionRenewalWorker
]
In Vigilmon, create a Heartbeat Monitor:
- Click New Monitor → Heartbeat
- Set expected interval: 90 minutes
- Copy the ping URL
- Set it as
VIGILMON_HEARTBEAT_URLin your environment
If the worker crashes or the Stripe API is unresponsive, the heartbeat stops — and Vigilmon alerts you before the next billing cycle is affected.
Step 6: Monitor Stripe webhook ingestion
Webhook delivery failures are silent by default. Add a separate monitor for your webhook endpoint:
# lib/my_app_web/controllers/stripe_webhook_controller.ex
defmodule MyAppWeb.StripeWebhookController do
use MyAppWeb, :controller
require Logger
def handle(conn, _params) do
payload = conn.assigns[:raw_body]
sig = get_req_header(conn, "stripe-signature") |> List.first()
secret = Application.get_env(:my_app, :stripe_webhook_secret)
case Stripe.Webhook.construct_event(payload, sig, secret) do
{:ok, event} ->
process_event(event)
send_resp(conn, 200, "ok")
{:error, reason} ->
Logger.error("[StripeWebhook] invalid signature: #{inspect(reason)}")
send_resp(conn, 400, "bad request")
end
end
defp process_event(%{type: "invoice.payment_succeeded"} = event) do
Logger.info("[StripeWebhook] payment succeeded for #{event.data.object.customer}")
end
defp process_event(%{type: "customer.subscription.deleted"} = event) do
Logger.info("[StripeWebhook] subscription cancelled: #{event.data.object.id}")
end
defp process_event(event) do
Logger.debug("[StripeWebhook] unhandled event type: #{event.type}")
end
end
Add a second HTTP monitor in Vigilmon pointing to https://yourdomain.com/webhooks/stripe with a POST method check to confirm the endpoint is accepting requests.
Step 7: Slack alerts and status page
Slack alerts:
- In Vigilmon, go to Notifications → New Channel → Slack
- Paste your Slack incoming webhook URL
- Enable the channel on your Stripe health monitor and subscription heartbeat
When Stripe connectivity drops:
🔴 DOWN: yourdomain.com/health
Keyword "status":"ok" not found
Detected: EU-West
1 minute ago
When the subscription worker misses a cycle:
🔴 HEARTBEAT MISSED: Subscription Renewal Worker
Expected ping within 90 minutes — none received
Last seen: 97 minutes ago
Status page:
- Go to Status Pages → New Status Page
- Add your HTTP monitor and heartbeat monitor
- Share with your team — or make it public for customers during incidents
What you've built
| What | How |
|------|-----|
| Stripe connectivity check | Account retrieve probe with latency |
| Uptime monitoring | Vigilmon HTTP monitor → /health |
| Keyword check | Vigilmon keyword match on "status":"ok" |
| Telemetry latency | Stripity Stripe Telemetry events + slow request logging |
| Subscription worker monitoring | Heartbeat GenServer + Vigilmon heartbeat monitor |
| Webhook endpoint monitoring | Vigilmon HTTP POST monitor |
| Slack alerts | Vigilmon Slack notification channel |
| Status page | Vigilmon public status page |
Stripity Stripe connects your app to Stripe's payment infrastructure. Vigilmon tells you when that connection — or the workers built on top of it — silently stops working.
Next steps
- Alert on Stripe API latency trending above 1 second using Vigilmon's response time history, which can signal key rotation issues or rate-limit pressure
- Monitor your Stripe webhook secret rotation by surfacing webhook signature failures in your health response
- Use Vigilmon's multi-region checks to confirm your payment endpoints are reachable from all regions where customers make purchases
Get started free at vigilmon.online.