Understanding Webhook Payloads: All webhook events are sent as JSON objects in the request body. Your endpoint should parse the JSON and respond with a 200 status code within 5 seconds.

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

Payload Structure JSON
{
  "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

Node.js/Express Handler javascript
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

Payload Structure JSON
{
  "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

Update Sales Pipeline Stage javascript
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

Payload Structure JSON
{
  "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

Track Communication History javascript
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

Payload Structure JSON
{
  "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

Track SMS Communications javascript
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');
});

recent_draft_project

Triggered when a draft project is created or updated. Useful for tracking work-in-progress.

Event Payload

Payload Structure JSON
{
  "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

Track Draft Projects javascript
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:

Subscribe with testing URL JSON
{
  "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 bash
# 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:

Async Processing Pattern javascript
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:

Idempotency Pattern javascript
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:

Logging Pattern javascript
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');
});
Security Reminders:
  • 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

Related Documentation