Webhook Events Reference
Complete reference for all webhook event types and their payload structures. Each event sends a JSON payload to your webhook URL via HTTP POST.
Event Types Overview
Event Type | Trigger | Typical Use Cases |
---|---|---|
customer_created |
New customer is added to the system | CRM sync, welcome emails |
customer_status_change |
Customer status is updated | Sales pipeline, notifications |
project_email_sent |
Email sent for a project | Communication tracking |
project_sms_sent |
SMS sent for a project | Communication tracking |
published_proposal_link |
Proposal link is published | Team notifications, analytics |
recent_draft_project |
Draft project created or updated | Work-in-progress tracking |
customer_created
Triggered when a new customer is created in Solar Proof.
Event Payload
{
"pkcustomerid": 12345,
"fkuserid": 67890,
"customer_email": "john.doe@example.com",
"customer_name": "John Doe",
"customer_type": "RESIDENTIAL",
"customer_status": 1
}
Payload Fields
Field | Type | Description |
---|---|---|
pkcustomerid |
integer | Unique customer ID in Solar Proof |
fkuserid |
integer | ID of the Solar Proof user who owns this customer |
customer_email |
string | Customer's email address |
customer_name |
string | Customer's full name |
customer_type |
string | Customer type: RESIDENTIAL , COMMERCIAL , or LARGE-SCALE |
customer_status |
integer | Status: 0=No Status, 1=New Contacts, 2=Signed, 3=Mega Status |
Example Handler
app.post('/webhooks/solarproof', async (req, res) => {
const event = req.body;
// Sync to CRM
await crm.createContact({
email: event.customer_email,
name: event.customer_name,
type: event.customer_type,
source: 'Solar Proof'
});
res.status(200).send('OK');
});
customer_status_change
Triggered when a customer's status is changed (e.g., from New Contacts to Signed).
Event Payload
{
"pkcustomerid": 12345,
"fkuserid": 67890,
"customer_email": "john.doe@example.com",
"customer_name": "John Doe",
"old_status": 1,
"new_status": 2,
"status_name": "Signed"
}
Payload Fields
Field | Type | Description |
---|---|---|
pkcustomerid |
integer | Unique customer ID in Solar Proof |
fkuserid |
integer | ID of the Solar Proof user who owns this customer |
customer_email |
string | Customer's email address |
customer_name |
string | Customer's full name |
old_status |
integer | Previous status: 0=No Status, 1=New Contacts, 2=Signed, 3=Mega Status |
new_status |
integer | New status: 0=No Status, 1=New Contacts, 2=Signed, 3=Mega Status |
status_name |
string | Human-readable name of the new status |
Example Handler
app.post('/webhooks/solarproof', async (req, res) => {
const event = req.body;
if (event.new_status === 2) {
// Customer signed - move to "Closed Won" stage
await salesPipeline.moveToStage(event.customer_email, 'closed_won');
await notifications.sendToSlack(`🎉 ${event.customer_name} signed!`);
}
res.status(200).send('OK');
});
project_email_sent
Triggered when an email is sent for a project (e.g., proposal email, follow-up).
Event Payload
{
"belongs_to_customer": 12345,
"belongs_to_user": 67890,
"activity_type": "email_sent",
"belongs_to_project": 54321
}
Payload Fields
Field | Type | Description |
---|---|---|
belongs_to_customer |
integer | ID of the customer who received the email |
belongs_to_user |
integer | ID of the Solar Proof user who sent the email |
activity_type |
string | Always "email_sent" for this event |
belongs_to_project |
integer | ID of the project (quotation) the email relates to |
Example Handler
app.post('/webhooks/solarproof', async (req, res) => {
const event = req.body;
await analytics.trackEvent('project_email_sent', {
customer_id: event.belongs_to_customer,
project_id: event.belongs_to_project,
user_id: event.belongs_to_user,
timestamp: new Date()
});
res.status(200).send('OK');
});
project_sms_sent
Triggered when an SMS is sent for a project.
Event Payload
{
"belongs_to_customer": 12345,
"belongs_to_user": 67890,
"activity_type": "sms_sent",
"belongs_to_project": 54321
}
Payload Fields
Field | Type | Description |
---|---|---|
belongs_to_customer |
integer | ID of the customer who received the SMS |
belongs_to_user |
integer | ID of the Solar Proof user who sent the SMS |
activity_type |
string | Always "sms_sent" for this event |
belongs_to_project |
integer | ID of the project (quotation) the SMS relates to |
Example Handler
app.post('/webhooks/solarproof', async (req, res) => {
const event = req.body;
await communicationLog.record({
type: 'sms',
customer_id: event.belongs_to_customer,
project_id: event.belongs_to_project,
sent_by: event.belongs_to_user,
timestamp: new Date()
});
res.status(200).send('OK');
});
published_proposal_link
Triggered when a proposal link is published and made accessible to a customer.
Event Payload
{
"belongs_to_customer": 12345,
"belongs_to_user": 67890,
"activity_type": "published_proposal_link",
"belongs_to_project": 54321
}
Payload Fields
Field | Type | Description |
---|---|---|
belongs_to_customer |
integer | ID of the customer who can access the proposal |
belongs_to_user |
integer | ID of the Solar Proof user who published the proposal |
activity_type |
string | Always "published_proposal_link" for this event |
belongs_to_project |
integer | ID of the project (quotation) that was published |
Example Handler
app.post('/webhooks/solarproof', async (req, res) => {
const event = req.body;
// Notify sales team
await slack.sendMessage('#sales', {
text: `📄 Proposal published for project ${event.belongs_to_project}`,
customer_id: event.belongs_to_customer
});
// Track in analytics
await analytics.track('proposal_published', {
project_id: event.belongs_to_project
});
res.status(200).send('OK');
});
recent_draft_project
Triggered when a draft project is created or updated. Useful for tracking work-in-progress.
Event Payload
{
"pkcustomerquotationid": 54321,
"fkuserid": 67890,
"quotenickname": "Smith Residence Solar",
"siteaddress": "123 Main St, Sydney NSW 2000"
}
Payload Fields
Field | Type | Description |
---|---|---|
pkcustomerquotationid |
integer | Unique project (quotation) ID |
fkuserid |
integer | ID of the Solar Proof user working on the draft |
quotenickname |
string | Nickname/name of the quote/project |
siteaddress |
string | Installation site address for the project |
Example Handler
app.post('/webhooks/solarproof', async (req, res) => {
const event = req.body;
await projectTracker.upsert({
project_id: event.pkcustomerquotationid,
nickname: event.quotenickname,
address: event.siteaddress,
user_id: event.fkuserid,
status: 'draft',
last_updated: new Date()
});
res.status(200).send('OK');
});
Testing Webhooks
To test webhook events during development:
1. Use a Webhook Testing Service
Services like webhook.site or RequestBin provide temporary URLs to inspect webhook payloads:
{
"hookUrl": "https://webhook.site/your-unique-id",
"type_request": "customer_created"
}
2. Use ngrok for Local Development
Expose your local development server to receive webhooks:
# Start ngrok tunnel
ngrok http 3000
# Use the ngrok URL in your webhook subscription
{
"hookUrl": "https://abc123.ngrok.io/webhooks/solarproof",
"type_request": "customer_created"
}
3. Create Test Events
Trigger events in your Solar Proof account to generate webhook payloads:
- Create a test customer to trigger
customer_created
- Change a customer's status to trigger
customer_status_change
- Send a project email to trigger
project_email_sent
Webhook Best Practices
Respond Quickly
Your webhook endpoint must respond within 5 seconds. Process complex operations asynchronously:
app.post('/webhooks/solarproof', async (req, res) => {
const event = req.body;
// Respond immediately
res.status(200).send('OK');
// Process asynchronously
await queue.add('process-webhook', event);
});
Handle Duplicate Events
Webhooks may be delivered more than once. Make your handlers idempotent:
app.post('/webhooks/solarproof', async (req, res) => {
const event = req.body;
const eventId = `${event.belongs_to_project}_${event.activity_type}`;
// Check if already processed
const processed = await cache.get(eventId);
if (processed) {
return res.status(200).send('Already processed');
}
// Process event
await handleEvent(event);
// Mark as processed (cache for 24 hours)
await cache.set(eventId, true, 86400);
res.status(200).send('OK');
});
Log Everything
Keep detailed logs for debugging and compliance:
app.post('/webhooks/solarproof', async (req, res) => {
const event = req.body;
await logger.info('Webhook received', {
event_type: event.activity_type || 'customer_event',
payload: event,
timestamp: new Date()
});
// Process event
res.status(200).send('OK');
});
- Always use HTTPS URLs for webhook endpoints
- Validate webhook payloads before processing
- Implement rate limiting to prevent abuse
- Monitor webhook endpoint health and uptime
- Set up alerts for failed webhook deliveries