Documentation Index
Fetch the complete documentation index at: https://rimp.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Webhooks let your backend react to Rimp events without polling. Every payload is HMAC-signed; verify the signature before trusting it.
Events
| Event | When |
|---|
generation.completed | Any image or video generation reaches succeeded |
generation.failed | Generation reaches failed (refund issued automatically) |
comparison.completed | All children of a MultiModelComparison reach terminal |
usage.budget_threshold | API key reaches 50%, 80%, or 100% of monthly_budget_usd |
usage.anomaly_detected | Spending anomaly detector pauses the org |
Subscribing
curl -X POST https://api.rimp.example/v1/webhooks \
-H "Authorization: Bearer $RIMP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.your-app.com/webhooks/rimp",
"events": ["generation.completed", "generation.failed"]
}'
Response (the secret is shown ONCE):
{
"id": "whk_abc",
"url": "https://api.your-app.com/webhooks/rimp",
"events": ["generation.completed", "generation.failed"],
"secret": "whsec_..."
}
Use "*" in the events array to subscribe to everything.
{
"id": "evt_abc",
"type": "generation.completed",
"created_at": "2026-05-19T18:24:01Z",
"data": {
"generation_id": "gen_xyz",
"modality": "video",
"status": "succeeded",
"charged_credits": 600
}
}
Always parse the id first — events are guaranteed unique per delivery attempt, so use it as your dedupe key.
Verifying the signature
Every delivery includes:
POST /webhooks/rimp HTTP/1.1
Content-Type: application/json
X-Rimp-Signature: t=1715900400,v1=ad8c1b7f...
X-Rimp-Event: generation.completed
X-Rimp-Delivery: whd_abc
The signature is HMAC-SHA-256 over ${timestamp}.${rawBody} with your webhook secret as the key.
import crypto from 'node:crypto';
function verify(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(
header.split(',').map((p) => p.split('=', 2) as [string, string]),
);
const ts = Number(parts.t);
const sig = parts.v1;
if (!ts || !sig) return false;
// Reject events older than 5 minutes (replay protection)
if (Math.abs(Date.now() / 1000 - ts) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${ts}.${rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(sig, 'hex'));
}
// Express handler
app.post('/webhooks/rimp', express.raw({ type: 'application/json' }), (req, res) => {
const ok = verify(
req.body.toString('utf8'),
req.headers['x-rimp-signature'] as string,
process.env.RIMP_WEBHOOK_SECRET!,
);
if (!ok) return res.status(401).end();
const event = JSON.parse(req.body.toString('utf8'));
// ... handle event
res.status(204).end();
});
Retry policy
If your endpoint doesn’t return 2xx within 15s, Rimp retries with exponential backoff:
| Attempt | Delay |
|---|
| 1 | immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 12 hours |
After 6 attempts the delivery is marked exhausted. You can replay any delivery from the dashboard.
Best practices
- Return 2xx fast. Acknowledge the event, then process async. Heavy work should never run in the webhook handler.
- Dedupe by
event.id. Same event can be delivered more than once during retries.
- Verify before trusting. Don’t parse the body before checking the signature.
- Pin the secret. Each webhook has its own
whsec_…. Store it in your env per environment.
- Listen for
* in dev. Easier than subscribing to each event individually.