> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ropes.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive notifications via HTTP callbacks after user-driven events

## Introduction

Ropes can send webhooks to your server when certain events occur during the candidate or assessment lifecycle. This allows you to build automated workflows, sync data with your systems, and react to candidate progress in real-time.

## Configuration

To enable webhooks, navigate to **Settings → Integrations** **→ Webhooks** and configure:

1. **Webhook URL** - The HTTPS endpoint where Ropes will send POST requests
2. **Webhook Secret** - A secret key used to sign webhook payloads for verification
3. **Subscribed Events** - Select which events you want to receive

## Events

You can subscribe to any combination of the following events:

| Event                        | Description                                               |
| ---------------------------- | --------------------------------------------------------- |
| `interview.sent`             | An assessment has been sent to a candidate                |
| `interview.started`          | A candidate has started their assessment                  |
| `interview.completed`        | A candidate has completed their assessment                |
| `interview.expired`          | An assessment has expired (with or without being started) |
| `problem.creation.completed` | Custom Ropes problem creation completed                   |
| `scout.completed`            | Scout finishes running on a candidate                     |

## Payload Structure

All `interview` webhook payloads include these base fields:

```json theme={null}
{
  "event": "interview.completed",
  "timestamp": "2025-11-25T14:30:00.000Z",
  "interviewId": "abc123",
  "email": "candidate@example.com",
  "problemId": "prob_456",
  "problemName": "React Dashboard Challenge"
}
```

Full example payloads for each specific event are below:

### Event-Specific Payloads

#### interview\.sent

```json theme={null}
{
  "event": "interview.sent",
  "timestamp": "2025-11-25T14:30:00.000Z",
  "interviewId": "abc123",
  "email": "candidate@example.com",
  "problemId": "prob_456",
  "problemName": "React Dashboard Challenge",
  "assessmentLink": "https://yourcompany.ropes.ai/assessment/abc123",
  "expiresAt": "2025-12-02T14:30:00.000Z"
}
```

| Field            | Type              | Description                      |
| ---------------- | ----------------- | -------------------------------- |
| `assessmentLink` | string            | The URL sent to the candidate    |
| `expiresAt`      | string (optional) | ISO 8601 expiration date, if set |

#### interview\.started

```json theme={null}
{
  "event": "interview.started",
  "timestamp": "2025-11-25T14:30:00.000Z",
  "interviewId": "abc123",
  "email": "candidate@example.com",
  "problemId": "prob_456",
  "problemName": "React Dashboard Challenge",
  "startedAt": "2025-11-25T14:30:00.000Z"
}
```

| Field       | Type   | Description                                   |
| ----------- | ------ | --------------------------------------------- |
| `startedAt` | string | ISO 8601 timestamp when the candidate started |

#### interview\.completed

```json theme={null}
{
  "event": "interview.completed",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "interviewId": "abc123-def456-ghi789",
  "email": "candidate@example.com",
  "problemId": "problem-123",
  "problemName": "Reverse Linked List",
  "completionDate": "2025-01-15T10:30:00.000Z",
  "finalScore": 85,
  "plagiarism": false,
  "reportLink": "https://yourorg.ropes.ai/results/abc123-def456-ghi789",
  "timeTaken": 2700,
  "integrationSource": "LOXO",
  "externalId": "external-job-123",
  "candidateExternalId": "candidate-ats-id-123"
}
```

| Field            | Type    | Description                                                                                                                    |
| ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `completionDate` | string  | ISO 8601 timestamp when the assessment was completed                                                                           |
| `finalScore`     | number  | The candidate's final score (0-100)                                                                                            |
| `plagiarism`     | boolean | The integrity risk of the assessment, called `plagiarism` for legacy compatibility reasons. Values are `low`, `medium`, `high` |
| `reportLink`     | string  | URL to view the detailed results                                                                                               |
| `timeTaken`      | number  | Time taken in seconds                                                                                                          |

#### interview\.expired

```json theme={null}
{
  "event": "interview.expired",
  "timestamp": "2025-11-25T14:30:00.000Z",
  "interviewId": "abc123",
  "email": "candidate@example.com",
  "problemId": "prob_456",
  "problemName": "React Dashboard Challenge",
  "expiredAt": "2025-11-25T14:30:00.000Z",
  "wasStarted": false
}
```

| Field        | Type    | Description                                                        |
| ------------ | ------- | ------------------------------------------------------------------ |
| `expiredAt`  | string  | ISO 8601 timestamp when the assessment expired                     |
| `wasStarted` | boolean | Whether the candidate had started the assessment before it expired |

#### problem.creation.completed

```json theme={null}
 {
    "event": "problem.creation.completed",
    "timestamp": "2026-05-26T12:00:00.000Z",
    "problemId": "test_problem_123",
    "organizationId": 42,
    "success": true
}
```

#### scout.completed

Recurring Scout runs emit the event only if the previously established risk level changes.

```json theme={null}
{
  "event": "scout.completed",
  "timestamp": "2026-07-14T20:15:00.000Z",
  "candidateName": "Alex Morgan",
  "email": "alex.morgan@example.com",
  "identifier": "ABC123",
  "scoutRiskLevel": "Review Recommende",
  "integrationSource": "jobdiva",
  "candidateExternalId": "987654"
}
```

## Security

### Signature Verification

Every webhook request includes an `x-ropes-webhook-signature` header containing an HMAC-SHA256 signature of the request body, signed with your webhook secret.

Use the following code as an example of how to conduct signature verification.

```javascript theme={null}
const crypto = require('crypto')

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex')

  return crypto.timingSafeEquals(Buffer.from(signature), Buffer.from(expectedSignature))
}

// In your webhook handler
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-ropes-webhook-signature']

  if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature')
  }

  // Process the webhook
  const { event, interviewId } = req.body
  console.log(`Received ${event} for interview ${interviewId}`)

  res.status(200).send('OK')
})
```

### Best Practices

* Always verify the webhook signature before processing
* Use HTTPS endpoints only
* Respond with a 2xx status code within 10 seconds
* Process webhooks asynchronously if they trigger long-running operations
* Store your webhook secret securely (e.g., environment variables)

## Delivery & Retries

Ropes will attempt to deliver each webhook up to **3 times**:

1. **Initial delivery** - Immediately when the event occurs
2. **First retry** - 1 minute after the initial failure
3. **Second retry** - 5 minutes after the first retry

A delivery is considered successful when your endpoint returns a 2xx HTTP status code. After all retry attempts are exhausted, the delivery is marked as failed.

### Timeout

Webhook requests timeout after **10 seconds**. Ensure your endpoint responds quickly to avoid unnecessary retries. Clients are responsible for handling idempotency on retries.

## Testing

During development, you can use tools like [webhook.site](https://webhook.site) or [ngrok](https://ngrok.com) to receive webhooks locally and inspect the payloads. Ropes supports sending **test webhooks** via the webhook settings page. This allows developers to iterate quickly when integrating Ropes data into their systems.
