Your analytics
Live data from your Otterpay checkout — last 30 days.
No orders in this range yet.
Developers
API keys, embed snippet, webhooks & delivery logs.
OpenClose
Developers
API keys, embed snippet, webhooks & delivery logs.
Use API keys to authenticate requests from your storefront. Drop the embed snippet onto your checkout page, or call the API from your backend. Webhooks let us notify you the moment a basket is paid.
API keys
test mode
Safe for development — baskets won't be sent to real fulfillment.
Publishable key
—Secret key
—live mode
Live mode — real merchants, real notifications.
Publishable key
—Secret key
—Embed on your site
Preview
Drop the snippet below — embed.js auto-styles any [data-otterpay] button into the pill above. Use data-variant="dark|outline" to switch styles.
HTML snippet
<script src="https://otterpay.app/embed.js" data-pk="pk_live_…"></script>
<button
data-otterpay
data-basket='{"basket_id":"B-123","total":42.00,"items":[{"sku":"X","name":"Tee","price":42,"qty":1}]}'
></button>
<!-- Variants: data-variant="dark" or data-variant="outline" -->
<!-- Custom label: data-label="Pay with a friend" -->cURL — create basket from your backend
curl https://otterpay.app/api/public/v1/baskets \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-d '{
"basket_id": "B-123",
"total_amount": 42.00,
"items": [{"sku":"X","name":"Tee","price":42,"qty":1}],
"success_url": "https://yoursite.com/thanks",
"cancel_url": "https://yoursite.com/cart"
}'Webhooks
We POST signed JSON to your endpoint on every basket status change. Verify the Otterpay-Signature header using your signing secret.
No webhooks configured yet.
Recent deliveries
| Event | Status | Code | Attempts | When |
|---|---|---|---|---|
| No deliveries yet. | ||||
Verify webhook signatures
Node.js
import crypto from "crypto";
const header = req.headers["otterpay-signature"]; // "t=...,v1=..."
const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
const expected = crypto.createHmac("sha256", SIGNING_SECRET)
.update(parts.t + "." + rawBody).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));