Exception Capture SDK Setup
Capture frontend and backend exceptions with RewindRewind's native SDKs, or use Sentry-compatible envelope tooling where an existing Sentry SDK is already installed.
Set up with an AI agent
Hand this runbook to Claude or Codex. It runs the CLI, confirms an admin key (and asks you for one if missing, driving account creation when needed), then wires up all three surfaces: front-end exceptions, back-end exceptions, and app events. The same guide is served raw at http://rewindrewind.com/agents.md, so an agent can fetch and follow it directly.
# Set up RewindRewind with an AI agent
RewindRewind captures back-end and front-end exceptions plus custom product events.
This guide is self-contained; follow it top to bottom. Never invent an API key.
## 1. Make sure there is an account and an admin key
Ask the user for their RewindRewind admin key (it starts with `rr_`).
If they do not have an account yet:
1. Ask the user for their email address.
2. Create the account (this emails them a sign-in link):
```
curl -X POST http://rewindrewind.com/agents/signup -H 'content-type: application/json' -d '{"email":"USER_EMAIL"}'
```
3. Tell the user: open your inbox and click the RewindRewind sign-in link.
4. Then have them mint an admin key: go to http://rewindrewind.com/organization/keys, click
"New key", copy the `rr_…` secret (shown only once), and paste it back to me.
## 2. Configure the CLI (or call the API directly)
Run the CLI straight from the public repo, no install needed:
```
npx github:bananatron/rewindrewindcli configure --api-key rr_xxx --base-url http://rewindrewind.com
npx github:bananatron/rewindrewindcli init # finds the project and fetches its public key
```
No project yet? `npx github:bananatron/rewindrewindcli projects create --name "My App"`
Using it often? Install it once with `npm install -g github:bananatron/rewindrewindcli` for a persistent `rewindrewind` (alias `rr`) command, and set long-term auth so you never pass the key again: `rewindrewind config set api-key-file /path/to/key` (or export `REWINDREWIND_API_KEY`).
Prefer raw HTTP? Send `Authorization: Bearer rr_xxx` to http://rewindrewind.com/api/* (see OpenAPI below).
## 3. Wire the three surfaces
- Front-end exceptions: add one `<script>` to your `<head>` (order-independent; safe to re-run on SPA / Hotwire-Turbo navigations):
```html
<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" });
</script>
```
The public key (`rrpub_xxx`) is safe to embed, like a Sentry DSN.
- Back-end exceptions: send with the public key, or `npx github:bananatron/rewindrewindcli exceptions send …`
- App events: `rewind.captureEvent("checkout.completed", { total: 42 })`
or `npx github:bananatron/rewindrewindcli events send --type checkout.completed --properties '{"total":42}'`
## 4. Verify
```
npx github:bananatron/rewindrewindcli verify
```
## Reference
- Full SDK docs: http://rewindrewind.com/docs/exception-capture-sdk
- OpenAPI (every endpoint): http://rewindrewind.com/openapi.json
- All CLI commands: `npx github:bananatron/rewindrewindcli --help`
Collector and keys
The hosted collector is https://rewindrewind.com. Self-hosted Workers can use their own Worker origin in the same SDK options.
- Use your project's public key (
rrpub_…) to send events, exceptions, and source maps. It is safe to embed in browser and server SDKs. - Restrict who can ingest by setting project-level allowed origins and allowed IPs on the API keys page.
- Admin API keys (
rr_…) are secrets for the management API only, for reading and managing data. Never use them for ingestion or put them in client code. - Set stable
environmentvalues such asproduction,staging, andpreview. - Set
releaseto the deployed artifact version, package version, build number, or git SHA.
REWIND_ENDPOINT=https://rewindrewind.com
REWIND_PROJECT_KEY=rrpub_xxx # public project key for all ingestion
REWIND_ENVIRONMENT=production
REWIND_RELEASE=web@1.14.0
# Only where you call the management API / CLI (never in client code):
REWIND_ADMIN_KEY=rr_xxx
Fastest setup: the CLI
One command finds your project, reads its public project key, and prints copy-paste setup for all three surfaces: front-end exceptions, back-end exceptions, and app events:
npx github:bananatron/rewindrewindcli init --api-key rr_your_admin_key
npx github:bananatron/rewindrewindcli verify
verify sends test data and confirms it lands. The CLI can also drive the entire management API; run npx github:bananatron/rewindrewindcli --help.
Reaching for it often? Install it once for a persistent rewindrewind (and rr) command:
npm install -g github:bananatron/rewindrewindcli
rewindrewind --help
For long-term auth, point the CLI at a file holding your admin key instead of passing it each time (rewindrewind config set api-key-file /path/to/key), or set REWINDREWIND_API_KEY in the environment. See the CLI README for the full resolution order.
Payload Shape
Native SDKs send Sentry-shaped JSON to POST /v1/exceptions and custom product events to POST /v1/events.
{
"environment": "production",
"release": "web@1.14.0",
"platform": "javascript",
"level": "error",
"message": "Checkout failed",
"exception": {
"type": "TypeError",
"value": "Checkout failed",
"stacktrace": [
{ "filename": "app.js", "function": "submitCheckout", "line": 120, "column": 19, "in_app": true }
]
},
"identity": { "id": "user_123", "email": "person@example.com" },
"tags": { "service": "checkout" },
"extra": { "cart_id": "cart_123" }
}
Event Capture
Send product, workflow, and audit moments to POST /v1/events.
await rewind.captureEvent("checkout.button_clicked", {
path: window.location.pathname,
plan: user.plan,
});
curl https://rewindrewind.com/v1/events \
-H "Authorization: Bearer $REWIND_PROJECT_KEY" \
-H "Content-Type: application/json" \
--data '{
"type": "checkout.button_clicked",
"environment": "production",
"release": "web@1.14.0",
"properties": { "path": "/checkout", "plan": "team" }
}'
JavaScript Browser Apps
npm install @rewindrewind/sdk
import { initRewind } from "@rewindrewind/sdk/browser";
export const rewind = initRewind({
endpoint: import.meta.env.VITE_REWIND_ENDPOINT ?? "https://rewindrewind.com",
apiKey: import.meta.env.VITE_REWIND_PROJECT_KEY,
environment: import.meta.env.MODE === "production" ? "production" : "preview",
release: import.meta.env.VITE_REWIND_RELEASE,
tags: { service: "web" },
});
The browser SDK captures window.error and window.unhandledrejection by default.
Breadcrumbs wrap fetch. To record network breadcrumbs the SDK instruments window.fetch, so rewind.js can appear in the call stack of requests it did not originate, e.g. a Facebook Pixel or analytics call that your CSP connect-src blocks. Seeing rewind.js in such a stack does not mean RewindRewind made the request; it is a bystander the stack trace passes through.
Because those third-party failures aren't your code, the SDK treats them as such: a failed cross-origin fetch is recorded as a warning breadcrumb and its TypeError: Failed to fetch rejection is dropped rather than reported as a phantom exception. A failed same-origin request is kept and capturable, since it can signal a real outage.
For your Content Security Policy, the only host the SDK talks to is your collector, so connect-src needs just https://rewindrewind.com (or your self-hosted Worker origin), never the rotating serverless hosts a third-party tracker may post to.
try {
await submitCheckout();
} catch (error) {
await rewind.captureException(error, {
route: window.location.pathname,
action: "submit_checkout",
});
throw error;
}
await rewind.captureEvent("checkout.button_clicked", {
path: window.location.pathname,
});
Attach users after login and clear them on logout.
rewind.setIdentity({ id: user.id, email: user.email });
rewind.setTags({ plan: user.plan });
rewind.setIdentity(undefined);
Sentry Browser Tooling
If a browser app already uses Sentry, keep the SDK and tunnel event envelopes through RewindRewind.
npm install @sentry/browser
import * as Sentry from "@sentry/browser";
Sentry.init({
dsn: "https://rewind-public@rewindrewind.com/1",
tunnel: `https://rewindrewind.com/v1/sentry/envelope?sentry_key=${encodeURIComponent(
import.meta.env.VITE_REWIND_PROJECT_KEY,
)}`,
environment: import.meta.env.MODE === "production" ? "production" : "preview",
release: import.meta.env.VITE_REWIND_RELEASE,
});
The tunnel endpoint currently ingests Sentry envelope items with "type": "event". Use native SDKs when you also want RewindRewind product events.
Node.js Apps
npm install @rewindrewind/sdk
import { initRewind } from "@rewindrewind/sdk/node";
export const rewind = initRewind({
endpoint: process.env.REWIND_ENDPOINT ?? "https://rewindrewind.com",
apiKey: process.env.REWIND_PROJECT_KEY!,
environment: process.env.REWIND_ENVIRONMENT ?? "production",
release: process.env.REWIND_RELEASE,
tags: { service: "api", runtime: "node" },
autoCapture: true,
});
autoCapture: true captures uncaughtException and unhandledRejection. Capture route and job boundaries explicitly so request metadata is preserved.
app.use((err, req, _res, next) => {
void rewind
.captureException(err, {
request: { method: req.method, url: req.originalUrl },
})
.finally(() => next(err));
});
Bun Apps
bun add @rewindrewind/sdk
import { initRewind } from "@rewindrewind/sdk/bun";
const rewind = initRewind({
endpoint: Bun.env.REWIND_ENDPOINT ?? "https://rewindrewind.com",
apiKey: Bun.env.REWIND_PROJECT_KEY!,
environment: Bun.env.REWIND_ENVIRONMENT ?? "production",
release: Bun.env.REWIND_RELEASE,
tags: { service: "edge-api", runtime: "bun" },
});
Bun.serve({
async fetch(req) {
try {
return await handleRequest(req);
} catch (error) {
await rewind.captureException(error, {
request: { method: req.method, url: new URL(req.url).pathname },
});
return new Response("Internal Server Error", { status: 500 });
}
},
});
Ruby Apps
gem install rewind_rewind
require "rewind_rewind"
REWIND = RewindRewind::Client.new(
api_key: ENV.fetch("REWIND_PROJECT_KEY"),
endpoint: ENV.fetch("REWIND_ENDPOINT", "https://rewindrewind.com"),
environment: ENV.fetch("RACK_ENV", "production"),
release: ENV["REWIND_RELEASE"],
tags: { service: "web", runtime: "ruby" }
)
begin
charge_customer(customer_id)
rescue => error
REWIND.capture_exception(error, {
action: "charge_customer",
customer_id: customer_id
})
raise
end
Rack apps can wrap the application with REWIND.rack.call(app). Background jobs can use REWIND.job_wrapper(queue: "critical").
Python Apps
The framework-agnostic rewind-rewind package installs from the hosted index (no PyPI account involved) and depends only on the standard library. It imports as rewind_rewind.
pip install --index-url https://rewindrewind.com/pypi/simple/ rewind-rewind
# requirements.txt:
# --extra-index-url https://rewindrewind.com/pypi/simple/
# rewind-rewind>=1.0.0
import rewind_rewind
rewind_rewind.init(
project_key=os.environ["REWIND_PROJECT_KEY"], # rrpub_...
environment="production",
release=os.environ.get("GIT_SHA"),
)
try:
process_invoice(invoice_id)
except Exception as error:
rewind_rewind.capture_exception(error, extra={"invoice_id": invoice_id})
raise
rewind_rewind.capture_event("invoice.paid", properties={"total": invoice.total})
Flask / WSGI
Wrap the app: app.wsgi_app = RewindMiddleware(app.wsgi_app) (from rewind_rewind.wsgi) to auto-capture unhandled request exceptions with request context.
Plain scripts & workers
Call rewind_rewind.capture_exception(e) in your except blocks. Captures never raise and run on a short timeout, so they won't stall a job.
in_app frames
Set project_root (defaults to the cwd) so culprits point at your code, not site-packages or the stdlib.
Direct HTTP Capture
curl https://rewindrewind.com/v1/exceptions \
-H "Authorization: Bearer $REWIND_PROJECT_KEY" \
-H "Content-Type: application/json" \
--data '{
"environment": "production",
"release": "api@1.0.0",
"platform": "custom",
"level": "error",
"message": "Example failure",
"exception": { "type": "ExampleError", "value": "Example failure", "stacktrace": [] },
"tags": { "service": "api" },
"extra": { "source": "curl" }
}'
Sentry Envelope Endpoint
Send Sentry event envelopes to POST /v1/sentry/envelope. Authenticate with your public project key: Authorization: Bearer rrpub_xxx or ?sentry_key=rrpub_xxx.
curl "https://rewindrewind.com/v1/sentry/envelope?sentry_key=$REWIND_PROJECT_KEY" \
-H "Content-Type: application/x-sentry-envelope" \
--data-binary @envelope.txt
Source Maps
Upload JavaScript source maps with your project's public key (rrpub_…) and use the same release value as the browser SDK.
curl https://rewindrewind.com/v1/source-maps \
-H "Authorization: Bearer $REWIND_PROJECT_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON'
{
"release": "web@1.14.0",
"file_name": "assets/app.js.map",
"content": "..."
}
JSON
Verification Checklist
- Trigger a handled test exception in every runtime.
- Trigger an unhandled browser exception and unhandled promise rejection.
- Trigger an unhandled backend exception in a non-production environment.
- Confirm events land in the intended project and environment.
- Send the same exception twice and confirm issue grouping.
- Confirm
release,service, andruntimetags are present. - Confirm browser requests are rejected from disallowed origins.
- Confirm no server key is present in built frontend assets.
- Upload a source map and verify stack frames resolve for that release.
Operational Guidance
- Initialize SDKs before application code that may throw.
- Capture at runtime boundaries: browser entrypoints, route handlers, job runners, message consumers, cron handlers, and CLI command wrappers.
- Rethrow after capture unless the application intentionally handles the error.
- Await capture or call
flush()before process exit, job completion, or page unload when losing the event would matter. - Keep tags low-cardinality. Put unique IDs in
extraor event properties, not tags. - Configure blocked property names for tokens, passwords, cookies, and secrets.
The repository also includes the longer Markdown guide at docs/exception-capture-sdk.md for maintainers.