Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.omnifence.ai/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks are delivered once with no automatic retries. This page covers how to build a reliable receiver that handles failures gracefully.

Delivery guarantees

  • Webhooks are delivered once with a 10-second timeout.
  • If your server does not respond within 10 seconds or returns a non-2xx status, the delivery is not retried.
  • The result is always available via the job status endpoint, regardless of webhook delivery outcome.
Do not rely on webhooks as your only mechanism for receiving results. Always fall back to polling the job status endpoint for critical workloads.

Handling delivery failures

Because webhooks are not retried, build a recovery mechanism into your integration:
  1. Persist job IDs on submission. Store the job_id returned from any of the modality endpoints (POST /moderate/prompt, POST /moderate/image, POST /moderate/video).
  2. Poll on startup or recover from failures. On application restart or after a webhook outage, query GET /job/{id} for any job IDs in a non-terminal state (queued or processing).
  3. Set a polling timeout. If a job has not completed after a reasonable period (for example, 5 minutes for prompt or image moderation, 15 minutes for video moderation), treat it as failed and investigate.
async function fetchMissingResults(pendingJobIds) {
  for (const jobId of pendingJobIds) {
    const response = await fetch(`https://api.evershield.ai/api/v1/job/${jobId}`, {
      headers: { Authorization: `Bearer ${process.env.STARS_API_KEY}` },
    });
    const job = await response.json();
    if (job.status === 'completed' || job.status === 'failed') {
      await processResult(job);
    }
  }
}

Idempotency

Your webhook handler may occasionally receive a duplicate delivery (for example, if your server responded slowly). Make your handler idempotent by checking whether you have already processed a given job_id before acting on it.
app.post('/webhooks/stars', async (req, res) => {
  const { job_id, status, is_prohibited } = req.body;

  // Return early if already processed
  if (await db.isProcessed(job_id)) {
    return res.status(200).send({ ok: true });
  }

  await processResult({ job_id, status, is_prohibited });
  await db.markProcessed(job_id);

  res.status(200).send({ ok: true });
});

Responding quickly

Webhook payloads are small, but any processing you do synchronously (database writes, downstream API calls) can push your response time past the 10-second timeout. Acknowledge receipt immediately and process asynchronously.
app.post('/webhooks/stars', async (req, res) => {
  // Acknowledge immediately
  res.status(200).send({ ok: true });

  // Process asynchronously (don't await)
  processResult(req.body).catch((err) => logger.error(err));
});

Security considerations

The webhook request does not include a signature. To prevent spoofed payloads:
  • Use HTTPS for your webhook URL.
  • Validate the job_id against your records before trusting the payload.
  • Optionally, verify the result by calling GET /job/{id} before acting on a webhook.

Hybrid approach

For maximum reliability, use webhooks for speed and polling as a fallback:
  1. Register a webhook to receive results immediately when a job completes.
  2. Store all submitted job_id values with a pending state in your database.
  3. Run a background job every few minutes to poll the job status endpoint for any IDs that remain pending longer than expected.