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

Configuration

Webhook endpoints are configured at the integrator environment level in the Embed Portal. You can add up to 5 endpoints per environment. Each endpoint has its own signing secret and can be enabled or disabled.
1

Navigate to Webhooks

Go to Embed Portal → Webhooks
2

Add 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 and signature verification

Test webhooks

Use Send Test Webhook on an endpoint to verify connectivity and signature verification. Test webhooks use:
  • X-Webhook-Event: test
  • A small JSON payload with event, message, and timestamp

Event Types

EventDescription
score.completedScoring finished successfully with results
score.failedScoring failed with error details
batch.completedAll jobs in a batch have finished (completed or failed)

Payload: score.completed

{
  "event": "score.completed",
  "tenantId": "acme-corp",
  "scoringJobId": "scoring_job_id",
  "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",
  "tenantId": "acme-corp",
  "scoringJobId": "scoring_job_id",
  "jobId": "job-123",
  "applicationId": "app-456",
  "error": {
    "code": "RESUME_FETCH_FAILED",
    "message": "Failed to fetch resume: URL expired or inaccessible"
  },
  "failedAt": "2025-01-15T10:30:15Z"
}

Payload: batch.completed

Sent when all scoring jobs in a batch have finished processing (completed or failed).
{
  "event": "batch.completed",
  "tenantId": "acme-corp",
  "batchId": "batch_abc123",
  "jobId": "job-123",
  "totalJobs": 50,
  "completedJobs": 48,
  "failedJobs": 2,
  "status": "PARTIAL_FAILURE",
  "completedAt": "2025-01-15T10:42:21Z"
}
Status values:
  • COMPLETED: All jobs succeeded
  • PARTIAL_FAILURE: Some jobs succeeded, some failed
  • FAILED: All jobs failed

Signature Verification

Webhooks are signed using HMAC-SHA256. Always verify signatures to ensure requests are from Nova. Headers:
  • X-Webhook-Signature: sha256=<hex_signature>
  • X-Webhook-Event: Event type (for example: SCORE_COMPLETED)
  • X-Webhook-Timestamp: ISO timestamp
import crypto from 'crypto';

function verifyWebhookSignature(
  payload: string,        // Raw request body
  signature: string,      // X-Webhook-Signature header (sha256=<hex>)
  secret: string          // Your webhook secret (whsec_xxx)
): boolean {
  const [scheme, value] = signature.split('=');
  if (scheme !== 'sha256' || !value) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

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

// Express.js example
app.post('/webhooks/nova', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-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 and jitter.
  • Up to 5 attempts
  • Minimum delay: 1 minute
  • Maximum delay: 30 minutes

After All Retries Fail

If all 5 delivery attempts fail, automatic retries stop. However, results are never lost:
What happensDetails
Results remain accessibleRetrieve anytime via GET /v1/jobs/{jobId}/applications/{applicationId}/scoring-jobs/{scoringJobId}
No further retriesAutomatic retries stop after the final attempt
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 { scoringJob } = await nova.get(
      `/v1/jobs/${app.jobId}/applications/${app.applicationId}/scoring-jobs/${app.scoringJobId}`
    );
    if (scoringJob.status === 'COMPLETED' || scoringJob.status === 'FAILED') {
      await processResult(scoringJob);
    }
  }
}

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 the scoring job endpoint as a backup.

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