Skip to main content

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

EventWhen
generation.completedAny image or video generation reaches succeeded
generation.failedGeneration reaches failed (refund issued automatically)
comparison.completedAll children of a MultiModelComparison reach terminal
usage.budget_thresholdAPI key reaches 50%, 80%, or 100% of monthly_budget_usd
usage.anomaly_detectedSpending 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.

Payload format

{
  "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:
AttemptDelay
1immediate
21 minute
35 minutes
430 minutes
52 hours
612 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.