Skip to main content
Scoring results are delivered asynchronously via webhooks. This page covers setup, verification, and best practices.

Configuration

Webhook URLs are configured at the integrator environment level in the Embed Portal - not per-request or per-tenant.
1

Navigate to Webhooks

Go to Embed Portal → Settings → Webhooks
2

Set Endpoint URL

Enter your HTTPS endpoint URL
3

Copy Signing Secret

Save the webhook signing secret (whsec_*) securely
4

Test Delivery

Use “Send Test Webhook” to verify connectivity

Event Types

EventDescription
score.completedScoring finished successfully with results
score.failedScoring failed with error details

Payload: score.completed

{
  "event": "score.completed",
  "scoringJobId": "sj_abc123def456",
  "jobId": "job-123",
  "applicationId": "app-456",
  "result": {
    "score": 7,
    "assessment": {
      "verdict": "Strong candidate with solid backend experience...",
      "strengths": [
        "6 years of backend engineering experience",
        "Strong Go proficiency"
      ],
      "concerns": [
        "No direct Kubernetes experience"
      ],
      "interviewFocus": [
        "Probe depth of distributed systems knowledge"
      ]
    }
  },
  "completedAt": "2025-01-15T10:30:45Z"
}

Payload: score.failed

{
  "event": "score.failed",
  "scoringJobId": "sj_abc123def456",
  "jobId": "job-123",
  "applicationId": "app-456",
  "error": {
    "type": "https://embed.nova.dweet.com/errors/resume-fetch-failed",
    "code": "RESUME_FETCH_FAILED",
    "message": "Failed to fetch resume: URL expired or inaccessible"
  },
  "failedAt": "2025-01-15T10:30:15Z"
}

Signature Verification

Webhooks are signed using HMAC-SHA256. Always verify signatures to ensure requests are from Nova. Header: X-Nova-Signature
import crypto from 'crypto';

function verifyWebhookSignature(
  payload: string,        // Raw request body
  signature: string,      // X-Nova-Signature header
  secret: string          // Your webhook secret (whsec_xxx)
): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Express.js example
app.post('/webhooks/nova', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-nova-signature'];
  const payload = req.body.toString();
  
  if (!verifyWebhookSignature(payload, signature, process.env.NOVA_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  
  const event = JSON.parse(payload);
  // Process the event...
  
  res.status(200).send('OK');
});
Always use timing-safe comparison functions (like timingSafeEqual or hmac.compare_digest) to prevent timing attacks.

IP Whitelisting

Signature verification is the primary security mechanism and is sufficient for most integrations. The HMAC-SHA256 signature cryptographically proves each request originated from Nova.
If your security policy requires IP whitelisting in addition to signature verification, contact us through the Embed Portal to request our current webhook source IP ranges.
Note that IP ranges may change with infrastructure updates. We recommend relying on signature verification as your primary security control, with IP whitelisting as an optional additional layer.

Retry Policy

Nova retries failed webhook deliveries with exponential backoff:
AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes

After All Retries Fail

If all 4 delivery attempts fail, automatic retries stop. However, results are never lost:
What happensDetails
Results remain accessibleRetrieve anytime via GET /v1/score/{scoringJobId}
Portal visibilityFailed deliveries appear in Embed Portal with full payload
Manual retryRetry individual deliveries from the portal once your endpoint is fixed
Always implement a polling fallback for robustness. Check for applications stuck in “scoring” status and poll their results after a timeout (e.g., 5 minutes).
// Fallback: Poll for results if webhook wasn't received
async function checkPendingScores() {
  const stuckApplications = await db.applications.findMany({
    where: {
      novaStatus: 'scoring',
      submittedAt: { lt: new Date(Date.now() - 5 * 60 * 1000) } // 5+ min ago
    }
  });

  for (const app of stuckApplications) {
    const result = await nova.get(`/v1/score/${app.scoringJobId}`);
    if (result.status === 'completed' || result.status === 'failed') {
      await processResult(result);
    }
  }
}

Best Practices

Return a 2xx response within 30 seconds. Do heavy processing asynchronously.
Use scoringJobId as an idempotency key. Webhooks may be delivered more than once.
Log the full payload and your processing result for debugging.
Webhook endpoints must use HTTPS with a valid certificate.
If webhooks fail consistently, poll GET /v1/score/{id} as a backup.

Debugging Webhooks

In the Embed Portal, you can:
  • View recent deliveries with status codes and response times
  • Inspect payloads for failed deliveries
  • Manually retry individual webhook deliveries
  • Send test webhooks to verify your endpoint

Webhook Endpoint Checklist

HTTPS endpoint with valid SSL certificate
Signature verification implemented
Returns 2xx within 30 seconds
Handles duplicate deliveries (idempotent)
Logs events for debugging
Fallback polling mechanism in place