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

# lead.updated

> Triggered when a lead's status changes

This event is triggered when a lead's status changes, whether through campaign automation or manual updates via the API.

## When This Event Fires

* Lead status is updated via the [Update Lead Status API](/api-reference/endpoint/patch-lead-status)
* Campaign automation changes lead status
* Lead progresses through the campaign sequence

## Payload

```json theme={null}
{
  "eventId": "evt_1708456789123_abc123def",
  "eventType": "lead.updated",
  "timestamp": "2024-02-24T10:30:00.000Z",
  "workspaceId": "ws_abc123xyz",
  "data": {
    "leadId": "lead_abc123",
    "campaignId": "camp_xyz789",
    "linkedinUrl": "https://www.linkedin.com/in/john-doe",
    "previousStatus": "REPLY_RECEIVED",
    "newStatus": "OPPORTUNITY"
  }
}
```

## Payload Fields

| Field | Type | Description |
| - | - | - |
| `eventId` | string | Unique event identifier for idempotency |
| `eventType` | string | Always `lead.updated` |
| `timestamp` | string | ISO 8601 timestamp when the status changed |
| `workspaceId` | string | Your workspace ID |
| `data.leadId` | string | The lead whose status changed |
| `data.campaignId` | string | The campaign this lead belongs to |
| `data.linkedinUrl` | string | LinkedIn profile URL of the lead |
| `data.previousStatus` | string | Status before the change |
| `data.newStatus` | string | New status after the change |

## Campaign Status Values

| Status | Description |
| - | - |
| `PENDING` | Lead added but no action taken yet |
| `PROCESSING` | Lead is being processed |
| `CONNECTION_SENT` | Connection request sent |
| `CONNECTION_ACCEPTED` | Connection accepted by lead |
| `MESSAGE_SENT` | Follow-up message sent |
| `REPLY_RECEIVED` | Lead has replied |
| `FOLLOWUP_SENT` | Follow-up message sent |
| `BLOCKED` | Lead blocked the sender |
| `PROFILE_UNREACHABLE` | Profile is unreachable |
| `RATE_LIMITED` | Rate limited by LinkedIn |
| `FAILED` | Action failed |
| `SUCCESS` | Campaign completed successfully |
| `UNSUBSCRIBED` | Lead unsubscribed |
| `IRRELEVANT` | Lead marked as irrelevant |
| `SKIPPED` | Lead was skipped |
| `DONE` | Lead journey completed |
| `MEETING_BOOKED` | Meeting has been booked |
| `OPPORTUNITY` | Marked as sales opportunity |
| `LIKED_POST` | Lead liked a post |

## Custom Lead Status Values

| Status | Description |
| - | - |
| `LEAD` | New lead (default) |
| `INTERESTED` | Lead has shown interest |
| `MEETING_BOOKED` | Meeting has been scheduled |
| `MEETING_COMPLETE_NOT_CLOSED` | Meeting completed but deal not closed |
| `CLOSED` | Deal closed/won |
| `WRONG_PERSON` | Wrong contact/person |
| `NOT_INTERESTED` | Lead is not interested |
| `NO_RESPONSE` | No response received |

## Use Cases

<CardGroup cols={2}>
  <Card title="CRM Sync" icon="database">
    Keep your CRM in sync with lead status changes
  </Card>

  <Card title="Pipeline Tracking" icon="chart-line">
    Track leads through your sales pipeline
  </Card>

  <Card title="Alerting" icon="bell">
    Alert team when leads become opportunities
  </Card>

  <Card title="Automation" icon="gears">
    Trigger external workflows based on status
  </Card>
</CardGroup>

## Example Handler

```javascript theme={null}
app.post('/webhooks/sendpilot', async (req, res) => {
  const event = req.body;
  
  if (event.eventType === 'lead.updated') {
    const { leadId, previousStatus, newStatus, linkedinUrl } = event.data;
    
    // Sync status to CRM
    await crm.updateLead(leadId, {
      status: mapToCustomStatus(newStatus),
      lastStatusChange: event.timestamp
    });
    
    // Alert on high-value transitions
    if (newStatus === 'OPPORTUNITY') {
      await slack.postMessage({
        channel: '#sales-opportunities',
        text: `🎯 New opportunity!\n` +
              `Lead: ${linkedinUrl}\n` +
              `Previous status: ${previousStatus}`
      });
    }
    
    // Handle negative outcomes
    if (newStatus === 'NOT_INTERESTED') {
      await analytics.track('lead_lost', {
        leadId,
        previousStatus
      });
    }
    
    console.log(`Lead ${leadId}: ${previousStatus} → ${newStatus}`);
  }
  
  res.status(200).send('OK');
});

function mapToCustomStatus(sendpilotStatus) {
  const mapping = {
    'PENDING': 'new',
    'CONNECTION_SENT': 'contacted',
    'CONNECTION_ACCEPTED': 'connected',
    'MESSAGE_SENT': 'in_conversation',
    'REPLY_RECEIVED': 'engaged',
    'OPPORTUNITY': 'qualified',
    'NOT_INTERESTED': 'closed_lost',
    'DONE': 'closed_won'
  };
  return mapping[sendpilotStatus] || 'unknown';
}
```

<Note>
  Status changes can be frequent as leads progress through campaign sequences. Consider batching or debouncing if you're making external API calls on each status change.
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.