Understanding Webhook Payloads: All webhook events are sent as JSON arrays containing 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 created 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
contract_signed Contract is signed by customer Sales tracking, notifications
recent_draft_project Draft project created or updated Work-in-progress tracking

customer_created

Triggered when a new customer is created in Solar Proof.

Note: Unlike other webhook events, customer_created sends a single object (not an array). Access fields directly without using [0].

Event Payload

Payload Structure (Single Object) JSON
{
  "customer_id": 141472,
  "customer_uid": "a1b2c3d4",
  "first_name": "Hussain",
  "surname": "Shahab",
  "customer_name": "Hussain Shahab",
  "phone_primary": "0412345678",
  "phone_secondary": "",
  "customer_email": "s.hussainshahab@gmail.com",
  "company_name": "",
  "company_phone": "",
  "abn": "",
  "gst_registered": 0,
  "customer_type_id": 1,
  "customer_status_id": 1,
  "site_address": "45 Thuringowa Dr, Kirwan QLD 4817, Australia",
  "customer_created_at": "2025-10-16T12:00:00Z",
  "customer_notes": "",
  "assigned_user_id": 16,
  "assigned_user_name": "Chris Taeni",
  "assigned_user_email": "phoenix.cst@gmail.com",
  "company_name_full": "Solar Proof"
}

Payload Fields

Field Type Description
customer_id integer Unique customer ID
customer_uid string Unique customer identifier (UID)
first_name string Customer's first name
surname string Customer's surname/last name
customer_name string Full customer name
customer_email string Customer email address
phone_primary string Primary phone number
phone_secondary string Secondary phone number
company_name string Customer's company name (if commercial)
gst_registered integer GST registration status (0 or 1)
customer_type_id integer Customer type: 1 = Residential, 2 = Commercial
customer_status_id integer Initial customer status ID
site_address string Customer's site address
customer_created_at string ISO 8601 timestamp when customer was created
assigned_user_id integer ID of the Solar Proof user assigned to this customer
assigned_user_name string Name of assigned user
company_name_full string Solar installation company name

Example Handler

Node.js/Express Handler javascript
app.post('/webhooks/solarproof', async (req, res) => {
  const customer = req.body; // Single object, NOT an array!
  
  // Sync to CRM
  await crm.createContact({
    id: customer.customer_id,
    email: customer.customer_email,
    firstName: customer.first_name,
    lastName: customer.surname,
    phone: customer.phone_primary,
    address: customer.site_address,
    assignedTo: customer.assigned_user_name,
    source: 'Solar Proof'
  });
  
  res.status(200).send('OK');
});

customer_status_change

Triggered when a customer's status is changed (e.g., from Lead to Signed).

Event Payload

Payload Structure (Array Format) JSON
[
  {
    "customer_id": 141472,
    "first_name": "Hussain",
    "surname": "Shahab",
    "customer_name": "Hussain Shahab",
    "phone_primary": "",
    "phone_secondary": "",
    "customer_email": "s.hussainshahab@gmail.com",
    "company_name": "",
    "company_phone": "",
    "abn": "",
    "gst_registered": 0,
    "customer_type_id": 1,
    "customer_status_id": 1,
    "site_address": "45 Thuringowa Dr, Kirwan QLD 4817, Australia",
    "customer_created_at": "2025-10-16T00:00:00Z",
    "customer_notes": "",
    "assigned_user_id": 16,
    "assigned_user_name": "Chris Taeni",
    "assigned_user_email": "phoenix.cst@gmail.com",
    "assigned_user_phone": "0411 549 054",
    "company_name_full": "Solar Proof",
    "customer_status_name": "Lead"
  }
]

Payload Fields

Field Type Description
customer_id integer Unique customer ID in Solar Proof
first_name string Customer's first name
surname string Customer's surname/last name
customer_name string Full customer name
customer_email string Customer email address
phone_primary string Primary phone number
customer_status_id integer New status ID
customer_status_name string New status name (e.g., "Lead", "Signed")
site_address string Customer's site address
assigned_user_id integer ID of the Solar Proof user assigned to this customer
assigned_user_name string Name of assigned user

Example Handler

Node.js/Express Handler javascript
app.post('/webhooks/solarproof', async (req, res) => {
  const events = req.body; // Array of events
  const event = events[0]; // Get first event
  
  // Update CRM status
  await crm.updateContact(event.customer_id, {
    status: event.customer_status_name,
    assignedTo: event.assigned_user_name
  });
  
  res.status(200).send('OK');
});

project_email_sent

Triggered when an email is sent for a project (e.g., proposal email, follow-up). Returns comprehensive project, customer, equipment, and company information.

Event Payload

Payload Structure (Array Format) JSON
[
  {
    "activity_id": 266268,
    "activity_type": "email_sent",
    "email_sent_at": "2025-10-16T12:37:44Z",
    "project_id": 118825,
    "project_name": "45 Thuringowa Dr - Solar project (#118825)",
    "project_address": "{\"streetnum\":\"45\",\"street\":\"Thuringowa Dr\",\"suburb\":\"Kirwan\",\"state\":\"QLD\",\"postcode\":\"4817\",\"country\":\"AU\"}",
    "proposal_url": null,
    "viewing_link": "uploads/fa00dfba91388ede4483011b455d93/pdfs/118825/proposal_118825.pdf",
    "signed_at": null,
    "installation_date": null,
    "system_size_kw": 0,
    "panel_quantity": 21,
    "panel_wattage": "400",
    "quote_amount": "20830.00",
    "inverter_quantity": 1,
    "battery_quantity": 1,
    "deposit_amount": 4166,
    "deposit_percentage": 20,
    "has_signed_pdf": 0,
    "panel_brand": "TCL SunPower Global",
    "panel_model": "SPR-MAX3-400",
    "panel_wattage_spec": "400",
    "inverter_brand": "SMA Australia Pty Ltd",
    "inverter_model": "STP10000TL-20",
    "inverter_capacity": "10",
    "inverter_series": "Sunny Tripower",
    "battery_brand": "Alpha-ESS",
    "battery_model": "SMILE-G3-BAT-10.1P",
    "battery_name": "Alpha-ESS SMILE-G3-BAT-10.1P",
    "battery_capacity": 10.1,
    "customer_id": 141472,
    "customer_name": "Hussain Shahab",
    "customer_email": "s.hussainshahab@gmail.com",
    "customer_phone": "",
    "customer_company_name": "",
    "user_id": 16,
    "company_name": "Solar Proof",
    "user_name": "Chris Taeni",
    "user_email": "phoenix.cst@gmail.com",
    "company_phone": "08 7160 0127",
    "signed_pdf_url": null,
    "payment_terms": {
      "type": "cash",
      "description": null,
      "term_months": 0,
      "repayment_amount": 0,
      "repayment_frequency": "Weekly"
    }
  }
]

Payload Fields

Field Type Description
Activity Information
activity_id integer Unique activity log ID
activity_type string Always "email_sent" for this event
email_sent_at string ISO 8601 timestamp when email was sent
Project Information
project_id integer Unique project/quotation ID
project_name string Name/nickname of the project
project_address string JSON-encoded installation site address
system_size_kw number System capacity in kilowatts
quote_amount string Total quote amount after rebates
deposit_amount number Deposit amount required
signed_at string ISO timestamp when contract was signed (null if not signed)
installation_date string ISO timestamp of planned installation (null if not set)
Equipment Details
panel_brand string Solar panel manufacturer
panel_model string Solar panel model number
panel_quantity integer Number of solar panels
panel_wattage string Wattage per panel
inverter_brand string Inverter manufacturer
inverter_model string Inverter model number
inverter_capacity string Inverter capacity in kW
battery_brand string Battery manufacturer (null if no battery)
battery_model string Battery model number (null if no battery)
battery_capacity number Battery capacity in kWh (null if no battery)
Customer Information
customer_id integer Unique customer ID
customer_name string Customer's full name
customer_email string Customer's email address
customer_phone string Customer's phone number
User/Company Information
user_id integer ID of the Solar Proof user who sent the email
user_name string Name of the Solar Proof user
company_name string Solar installation company name
company_phone string Company phone number
Payment Information
payment_terms object Payment terms object containing type, description, term_months, repayment_amount, and repayment_frequency

Example Handler

Track Communication History javascript
app.post('/webhooks/solarproof', async (req, res) => {
  const events = req.body;
  const event = events[0];
  
  await analytics.trackEvent('project_email_sent', {
    customer_id: event.customer_id,
    project_id: event.project_id,
    user_id: event.user_id,
    system_size: event.system_size_kw,
    quote_amount: event.quote_amount,
    timestamp: new Date(event.email_sent_at)
  });
  
  res.status(200).send('OK');
});

project_sms_sent

Triggered when an SMS is sent for a project. Returns comprehensive project and customer information.

Event Payload

Payload Structure (Array Format) JSON
[
  {
    "activity_id": 264533,
    "activity_type": "sms_sent",
    "sms_sent_at": "2025-10-10T14:23:52Z",
    "project_id": 117573,
    "project_name": "48 Wright St - Battery project (#117573)",
    "project_address": "{\"streetnum\":\"48\",\"street\":\"Wright St\",\"suburb\":\"Paradise\",\"state\":\"SA\",\"postcode\":\"5075\",\"country\":\"AU\"}",
    "proposal_url": null,
    "viewing_link": "uploads/fa00dfba91388ede4483011b455d93/pdfs/117573/proposal_117573.pdf",
    "signed_at": "2025-10-15T00:00:00Z",
    "installation_date": null,
    "system_size_kw": 0,
    "quote_amount": "14974.00",
    "panel_quantity": 28,
    "inverter_quantity": 1,
    "battery_quantity": 1,
    "deposit_amount": 4000,
    "deposit_percentage": 20,
    "has_signed_pdf": 1,
    "panel_brand": "Trina Solar Co Ltd",
    "panel_model": "TSM-450NEG9R.25",
    "panel_wattage_spec": "450",
    "inverter_brand": "FOX ESS",
    "inverter_model": "KH10/KA10",
    "inverter_capacity": "10",
    "battery_brand": "Fox ESS",
    "battery_model": "ECS4800-H7",
    "battery_capacity": 32.61,
    "customer_id": 141145,
    "customer_name": "John Wilkinson",
    "customer_email": "jwilksy@solarproof.com.au",
    "customer_phone": "0411 222 222",
    "customer_phone_alternate": "",
    "customer_company_name": "",
    "user_id": 16,
    "company_name": "Solar Proof",
    "user_name": "Chris Taeni",
    "user_email": "phoenix.cst@gmail.com",
    "company_sender_phone": "08 7160 0127",
    "signed_pdf_url": "https://solarproof.com.au/uploads/fa00dfba91388ede4483011b455d93/pdfs/117573/proposal_117573.pdf",
    "payment_terms": {
      "type": "cash",
      "description": "",
      "term_months": 0,
      "repayment_amount": 0,
      "repayment_frequency": "Weekly",
      "balloon_payment": 0
    }
  }
]

Payload Fields

Similar fields to project_email_sent, with sms_sent_at instead of email_sent_at, and includes company_sender_phone (the phone number used to send the SMS).

Example Handler

Track SMS Communications javascript
app.post('/webhooks/solarproof', async (req, res) => {
  const events = req.body;
  const event = events[0];
  
  await communicationLog.record({
    type: 'sms',
    customer_id: event.customer_id,
    project_id: event.project_id,
    sent_by: event.user_name,
    sent_from: event.company_sender_phone,
    timestamp: new Date(event.sms_sent_at)
  });
  
  res.status(200).send('OK');
});

contract_signed

Triggered when a customer signs a contract/proposal. Includes extensive project details and customer information.

Event Payload

Payload Structure (Array Format) JSON
[
  {
    "activity_id": 265815,
    "activity_type": "document_signed",
    "contract_signed_at": "2025-10-15T03:46:15Z",
    "project_id": 118692,
    "project_name": "48 Wright St - Upgrade Battery project",
    "project_address": "{\"streetnum\":\"48\",\"street\":\"Wright St\",\"suburb\":\"Paradise\",\"state\":\"SA\",\"postcode\":\"5075\",\"country\":\"AU\"}",
    "proposal_url": null,
    "viewing_link": "uploads/fa00dfba91388ede4483011b455d93/pdfs/118692/proposal_118692.pdf",
    "signed_at": "2025-10-15T00:00:00Z",
    "installation_date": null,
    "system_size_kw": 0,
    "panel_quantity": 28,
    "panel_wattage": "450",
    "quote_amount": "14974.00",
    "inverter_quantity": 1,
    "battery_quantity": 1,
    "deposit_amount": 4000,
    "deposit_percentage": 20,
    "stc_value": 37,
    "stc_quantity": 376,
    "has_signed_pdf": 1,
    "has_original_pdf": 1,
    "panel_brand": "Trina Solar Co Ltd",
    "panel_model": "TSM-450NEG9R.25",
    "panel_wattage_spec": "450",
    "inverter_brand": "FOX ESS",
    "inverter_model": "KH10/KA10",
    "inverter_capacity": "10",
    "inverter_series": "KH/KA",
    "battery_brand": "Fox ESS",
    "battery_model": "ECS4800-H7",
    "battery_name": "Fox ESS ECS4800-H7",
    "battery_capacity": "32.61",
    "customer_id": 141201,
    "customer_name": "Julia Foghorn",
    "customer_first_name": "Julia",
    "customer_last_name": "Foghorn",
    "customer_email": "juliafoghorn@solarproof.com.au",
    "customer_phone": "0499 333 444",
    "customer_phone_alternate": "",
    "customer_company_name": "",
    "customer_company_phone": "",
    "customer_abn": "",
    "user_id": 16,
    "company_name": "Solar Proof",
    "user_name": "Chris Taeni",
    "user_email": "phoenix.cst@gmail.com",
    "company_phone": "08 7160 0127",
    "company_email": "admin@solarproof.com.au",
    "company_address": "764 Cudgen Rd, Kingscliff NSW 2487, Australia",
    "signed_pdf_url": null,
    "proposal_link": "https://solarproof.com.au/uploads/fa00dfba91388ede4483011b455d93/pdfs/118692/proposal_118692.pdf",
    "payment_terms": {
      "type": "cash",
      "description": "",
      "term_months": "0",
      "repayment_amount": "0",
      "repayment_frequency": "Weekly",
      "balloon_payment": "0"
    },
    "customer_type": "Residential",
    "customer_gst_status": "Not GST Registered"
  }
]

Additional Fields

Field Type Description
contract_signed_at string ISO timestamp when contract was signed
stc_value number Small-scale Technology Certificate value
stc_quantity number Number of STCs
customer_type string "Residential" or "Commercial"
customer_gst_status string "GST Registered" or "Not GST Registered"
company_email string Installation company email
company_address string Installation company address

Example Handler

Process Signed Contract javascript
app.post('/webhooks/solarproof', async (req, res) => {
  const events = req.body;
  const event = events[0];
  
  // Update CRM
  await crm.markAsSigned(event.customer_id, {
    signedDate: event.contract_signed_at,
    projectId: event.project_id,
    quoteAmount: event.quote_amount,
    depositAmount: event.deposit_amount
  });
  
  // Notify installation team
  await notifications.sendToTeam({
    subject: 'New Contract Signed!',
    customer: event.customer_name,
    systemSize: event.system_size_kw,
    installDate: event.installation_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 (Array Format) JSON
[
  {
    "project_id": 118871,
    "project_name": "80 Collins St - Solar project (#118871)",
    "project_address": "{\"streetnum\":\"132\",\"street\":\"Grenfell St\",\"suburb\":\"Adelaide\",\"state\":\"SA\",\"postcode\":\"5000\",\"country\":\"AU\"}",
    "proposal_url": null,
    "created_at": "2025-10-16T03:35:10Z",
    "last_edited": "2025-10-16T14:43:53Z",
    "system_size_kw": 0,
    "panel_quantity": 0,
    "panel_wattage": "250",
    "quote_amount": "6500.00",
    "inverter_quantity": 1,
    "battery_quantity": 0,
    "deposit_amount": 0,
    "deposit_percentage": 0,
    "is_draft": 1,
    "draft_stage": 8,
    "panel_brand": "Jinko Solar Co Ltd",
    "panel_model": "JKM250P-60",
    "panel_wattage_spec": "250",
    "inverter_brand": "Fronius International GmbH",
    "inverter_model": "Symo 10.0-3-M",
    "inverter_capacity": "10",
    "inverter_series": "Symo",
    "battery_brand": null,
    "battery_model": null,
    "battery_name": null,
    "battery_capacity": null,
    "customer_id": null,
    "customer_name": null,
    "customer_email": null,
    "customer_phone": null,
    "customer_company_name": null,
    "user_id": 53179,
    "company_name": "Solar Proof",
    "user_name": "Sam Lawson",
    "user_email": "sam@solarproof.com.au",
    "payment_terms": {
      "type": "cash",
      "description": "",
      "term_months": 0,
      "repayment_amount": 0,
      "repayment_frequency": "Weekly",
      "balloon_payment": 0
    }
  }
]

Key Payload Fields

Field Type Description
is_draft integer Always 1 for draft projects
draft_stage integer Current stage of the draft (1-10)
created_at string ISO timestamp when draft was created
last_edited string ISO timestamp of last modification
customer_id null May be null if customer not yet assigned

Example Handler

Track Draft Progress javascript
app.post('/webhooks/solarproof', async (req, res) => {
  const events = req.body;
  const event = events[0];
  
  await dashboard.updateDraft({
    projectId: event.project_id,
    projectName: event.project_name,
    stage: event.draft_stage,
    lastEdited: event.last_edited,
    assignedTo: event.user_name
  });
  
  res.status(200).send('OK');
});

Testing Webhooks

Create Test Events

Trigger events in your Solar Proof account to generate webhook payloads:

  • Change a customer's status to trigger customer_status_change
  • Send a project email to trigger project_email_sent
  • Send an SMS to trigger project_sms_sent
  • Publish a proposal to trigger published_proposal_link
  • Create or edit a draft project to trigger recent_draft_project

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 events = req.body;
  
  // Respond immediately
  res.status(200).send('OK');
  
  // Process asynchronously
  await queue.add('process-webhook', events);
});

Handle Array vs Object Format

Important: Most webhook payloads are sent as arrays, but customer_created sends a single object. Handle each accordingly:

Correct Way to Access Data javascript
app.post('/webhooks/solarproof', async (req, res) => {
  const payload = req.body;
  
  // Check if it's customer_created (single object)
  if (payload.customer_id && !Array.isArray(payload)) {
    // Direct access for customer_created
    console.log(payload.customer_id);
  } 
  // All other events (array format)
  else if (Array.isArray(payload)) {
    const event = payload[0];
    console.log(event.customer_id);
  }
  
  res.status(200).send('OK');
});

Handle Duplicate Events

Webhooks may be delivered more than once. Use idempotency keys to prevent duplicate processing:

Idempotency Pattern javascript
app.post('/webhooks/solarproof', async (req, res) => {
  const events = req.body;
  const event = events[0];
  const idempotencyKey = `${event.activity_type}_${event.activity_id}`;
  
  // Check if already processed
  const exists = await db.checkProcessed(idempotencyKey);
  if (exists) {
    return res.status(200).send('Already processed');
  }
  
  // Process and mark as complete
  await processEvent(event);
  await db.markProcessed(idempotencyKey);
  
  res.status(200).send('OK');
});

Parse JSON Addresses

The project_address field contains JSON-encoded address data. Parse it to access individual components:

Parse Address Field javascript
const events = req.body;
const event = events[0];

// Parse the address JSON
const address = JSON.parse(event.project_address);

console.log(address.streetnum); // "45"
console.log(address.street);    // "Thuringowa Dr"
console.log(address.suburb);    // "Kirwan"
console.log(address.state);     // "QLD"
console.log(address.postcode);  // "4817"
console.log(address.country);   // "AU"