Before you start
Sign in and create a project, then open Setup on the project to copy its keys. A few ideas carry across every SDK below.
The collector
All SDKs send to the hosted collector at https://rewindrewind.com. If you run RewindRewind yourself, point the same SDK options at your own Worker origin instead.
Two kinds of keys
Public project key
Starts with rrpub_. It authenticates all ingestion: events, exceptions, and source maps. It is safe to embed in browser and server code, much like a Sentry DSN. Limit who can send by setting allowed origins and IPs on the project.
Admin API key
Starts with rr_. It is a secret for the management API and CLI only, used to read and manage data. Never put it in client code or use it for ingestion.
Environment and release
Set a stable environment such as production, staging, or preview, and set release to the deployed version, build number, or git SHA. Both are optional, but they make issues far easier to triage and let stack traces line up with the right source maps.
Front-end exceptions Step 1
Add one script to the <head> of your pages. It captures uncaught errors and unhandled promise rejections automatically, attaches breadcrumbs for the clicks and requests that led up to a crash, and symbolicates stack traces with your source maps. There is no build step and no npm install.
<script>
(function (w, d) {
var r = (w.RewindRewind = w.RewindRewind || { _q: [] });
["init", "captureException", "captureEvent", "captureMessage", "addBreadcrumb", "setIdentity", "setTags", "setContext", "flush"]
.forEach(function (m) { r[m] = r[m] || function () { (r._q = r._q || []).push([m, arguments]); }; });
if (!r._loading) { r._loading = 1; var s = d.createElement("script"); s.async = 1; s.src = "http://rewindrewind.com/sdk/v1/rewind.js"; d.head.appendChild(s); }
})(window, document);
RewindRewind.init({
key: "rrpub_xxx",
environment: "production",
release: "my-app@1.0.0",
});
</script>What you get automatically
- Uncaught exceptions and unhandled promise rejections.
- Breadcrumbs: recent navigations, clicks, console output, and network calls.
- Readable stack traces when you upload source maps for the release.
- Release and environment tags on every issue.
Single-page apps and Hotwire Turbo
The loader is order-independent and init() is idempotent, so it is safe to re-run on client-side navigations and body swaps. A second init() updates options rather than installing a second set of handlers.
Capture something by hand
Caught an error you still want to see, or want richer context? Call the SDK directly. The same methods are available on the global RewindRewind object.
RewindRewind.setIdentity({ id: "user_1", email: "you@example.com" });
RewindRewind.addBreadcrumb({ category: "ui", message: "Opened checkout" });
try {
doRiskyThing();
} catch (err) {
RewindRewind.captureException(err, { tags: { area: "checkout" } });
}Already running Sentry's browser SDK? You can keep it and point its DSN at RewindRewind instead of swapping SDKs. See the Sentry-compatible setup in the full SDK reference.
Back-end exceptions Step 2
Add the SDK for your stack. After one configure block, most frameworks auto-capture unhandled request exceptions; you can also capture anything by hand. Pick your language or framework.
Add the gem, then one configure block. The bundled Railtie auto-captures unhandled request exceptions and everything reported through Rails.error, with no manual middleware.
# Gemfile
source "https://rewindrewind.com/gems" do
gem "rewind_rewind-rails"
end
# $ bundle install
# config/initializers/rewind_rewind.rb
RewindRewind.configure do |c|
c.api_key = "rrpub_xxx" # project public key (safe to embed)
c.environment = Rails.env
c.release = ENV["GIT_SHA"]
c.enabled = Rails.env.production?
endStay up to date
Your normal tooling and Dependabot/Renovate discover new versions from rewindrewind.com.
# bundle update rewind_rewind rewind_rewind-rails
# .github/dependabot.yml (auto-PR new versions):
registries:
rewindrewind:
type: rubygems-server
url: https://rewindrewind.com/gems
updates:
- package-ecosystem: bundler
directory: "/"
registries: [rewindrewind]
schedule: { interval: weekly }Framework-agnostic core for plain Ruby, Sinatra, Roda, or any Rack app. Ships RewindRewind::Rack middleware for Rack apps.
# Gemfile
source "https://rewindrewind.com/gems" do
gem "rewind_rewind"
end
require "rewind_rewind"
RewindRewind.configure do |c|
c.api_key = "rrpub_xxx"
c.environment = "production"
end
begin
risky!
rescue => e
RewindRewind.capture_exception(e)
end
# Rack apps: use RewindRewind::Rack as middleware.Stay up to date
Your normal tooling and Dependabot/Renovate discover new versions from rewindrewind.com.
# bundle update rewind_rewind
# .github/dependabot.yml (auto-PR new versions):
registries:
rewindrewind:
type: rubygems-server
url: https://rewindrewind.com/gems
updates:
- package-ecosystem: bundler
directory: "/"
registries: [rewindrewind]
schedule: { interval: weekly }Zero-dependency SDK for Node, Bun, and Cloudflare Workers. .install() hooks the global handlers; or capture manually.
# bun add https://rewindrewind.com/sdk/node/latest.tgz
# npm i https://rewindrewind.com/sdk/node/latest.tgz
import { init, captureException } from "@rewindrewind/node";
init({ projectKey: "rrpub_xxx", environment: "production" }).install();
// .install() hooks uncaughtException + unhandledRejection
try {
riskyWork();
} catch (err) {
await captureException(err);
}Stay up to date
Your normal tooling and Dependabot/Renovate discover new versions from rewindrewind.com.
# npm outdated @rewindrewind/node (or: bun outdated)
# .github/dependabot.yml (auto-PR new versions):
registries:
rewindrewind:
type: npm-registry
url: https://rewindrewind.com/npm/
updates:
- package-ecosystem: npm
directory: "/"
registries: [rewindrewind]
schedule: { interval: weekly }Zero-dependency SDK for any Python app (Flask, Bottle, plain scripts). Ships a pure WSGI middleware for web apps, or capture manually. No Django required.
# pip install --index-url https://rewindrewind.com/pypi/simple/ rewind-rewind
# requirements.txt:
# --extra-index-url https://rewindrewind.com/pypi/simple/
# rewind-rewind
import rewind_rewind
rewind_rewind.init(project_key="rrpub_xxx", environment="production")
# WSGI apps (Flask, Bottle, ...): wrap the app to auto-capture.
from rewind_rewind.wsgi import RewindMiddleware
app.wsgi_app = RewindMiddleware(app.wsgi_app)
# ...or capture manually:
try:
risky_work()
except Exception as e:
rewind_rewind.capture_exception(e)Stay up to date
Your normal tooling and Dependabot/Renovate discover new versions from rewindrewind.com.
# pip install --upgrade --index-url https://rewindrewind.com/pypi/simple/ rewind-rewind
# .github/dependabot.yml (auto-PR new versions):
registries:
rewindrewind:
type: python-index
url: https://rewindrewind.com/pypi/simple/
updates:
- package-ecosystem: pip
directory: "/"
registries: [rewindrewind]
schedule: { interval: weekly }Idiomatic Go SDK via a vanity import. Wrap your HTTP handler to capture panics, or capture errors manually.
// go get rewindrewind.com/go
import rewindrewind "rewindrewind.com/go"
rewindrewind.Init(rewindrewind.Config{
Key: "rrpub_xxx",
Environment: "production",
})
// Wrap your HTTP handler to capture panics:
handler = rewindrewind.Middleware(handler)
// ...or capture manually:
if err != nil {
rewindrewind.CaptureException(err)
}Stay up to date
Your normal tooling and Dependabot/Renovate discover new versions from rewindrewind.com.
# go get -u rewindrewind.com/go
# .github/dependabot.yml (tagged releases, no extra config):
updates:
- package-ecosystem: gomod
directory: "/"
schedule: { interval: weekly }No SDK for your runtime? POST directly with the public key.
curl http://rewindrewind.com/v1/exceptions \
-H "Authorization: Bearer rrpub_xxx" \
-H "Content-Type: application/json" \
-d '{"environment":"production","platform":"other","level":"error","message":"Example","exception":{"type":"Error","value":"Example","stacktrace":[]}}'Set environment and release here too. Matching the release you used when uploading source maps is what turns a minified back-end or front-end trace into readable file and line numbers.
Server events Step 3
Events are the product and workflow moments you choose to record: a signup, a checkout, a background job finishing. They share the same project key, the same timeline, and the same per-event price as exceptions, so you see what users did and what broke in one place. Once the SDK is initialized from Step 2, recording an event is a single call.
captureEvent(type, properties, options). Returns a promise; await it in short-lived workers so the request is sent before the process exits.
import { captureEvent } from "@rewindrewind/node";
await captureEvent("checkout.completed", {
total_cents: 4200,
currency: "usd",
items: 3,
}, { identityId: user.id });Properties go under properties:. identity_id ties the event to a user; source labels where it came from.
RewindRewind.capture_event(
"invoice.paid",
properties: { amount_cents: 4200, currency: "usd" },
identity_id: current_user.id,
source: "backend"
)Keyword-only arguments after the event type. properties is a plain dict of small, flat values.
rewind_rewind.capture_event(
"subscription.renewed",
properties={"plan": "pro", "amount_cents": 4200},
identity_id=user.id,
)CaptureEvent(type, props). Use CaptureEventContext to carry a request context.
rewindrewind.CaptureEvent("order.shipped", map[string]any{
"order_id": order.ID,
"carrier": "ups",
})Record product moments straight from the page. Calls queue before the SDK finishes loading, so this is safe anywhere after the loader snippet.
RewindRewind.captureEvent("cart.checkout_clicked", {
total_cents: 4200,
currency: "usd",
});No SDK for your runtime? POST the event with the public key. type is the only required field.
curl http://rewindrewind.com/v1/events \
-H "Authorization: Bearer rrpub_xxx" \
-H "Content-Type: application/json" \
-d '{"type":"checkout.completed","environment":"production","identity_id":"user_1","properties":{"total_cents":4200}}'What makes a good event
- A stable, dot-separated
typelikecheckout.completedorjob.failed. Keep the name steady so the timeline stays comparable over time. - A small, flat set of
properties. Numbers, strings, and booleans read best; avoid large blobs. - An
identity_idwhen the event belongs to a user, so events and issues line up against the same person.
Do not report expected control-flow errors as exceptions. Record a business moment as an event and reserve exceptions for failures that need triage. That keeps your Issues list to things worth fixing.
Verify it works
Send one of each and confirm it lands. The fastest path is the CLI, which sends test data and checks it arrives:
npx github:bananatron/rewindrewindcli verifyOr confirm by hand:
- Throw a test error in the browser and watch it appear under Issues.
- Raise a test exception on the server and confirm the stack trace is readable.
- Send a test event with the HTTP snippet above and find it under Events within a few seconds.
Reference
Going deeper, or wiring this up from an agent?