Skip to main content
Triggered when one or more signals you’ve configured as triggers change their value. This event delivers the updated signal data for all subscribed signals in the payload.
All Configured Signals Are Always IncludedEvery VEHICLE_STATE event contains all signals you configured in your webhook subscription, regardless of which signal(s) triggered the event. This ensures you always have complete vehicle state data.Example: If your webhook subscribes to 10 signals but only 1 trigger changes, you’ll receive all 10 signals in the payload.

When This Event Fires

The VEHICLE_STATE event fires when Smartcar detects a signal value change for any trigger signal configured in your webhook.

Trigger Configuration

You configure which signals should trigger this event through the Smartcar Dashboard when creating or editing a webhook. For example: Triggers:
  • TractionBattery.StateOfCharge (code: tractionbattery-stateofcharge)
  • Charge.IsCharging (code: charge-ischarging)
  • Location.PreciseLocation (code: location-preciselocation)
Subscribed Signals:
  • TractionBattery.StateOfCharge
  • Charge.IsCharging
  • Location.PreciseLocation
  • Charge.Voltage
  • Odometer.TraveledDistance
If any of the trigger signals change, a VEHICLE_STATE event is delivered containing all five subscribed signals.

Identifying Which Triggers Fired

The webhook payload includes a triggers field that specifies which trigger signal(s) changed to prompt this delivery. This helps you identify what caused the event without comparing all signal values.

Trigger Types

When a monitored trigger signal changes value, the triggers array contains the signal(s) that changed.
{
  "triggers": [
    {
      "code": "tractionbattery-stateofcharge",
      "name": "StateOfCharge",
      "group": "TractionBattery"
    }
  ]
}
If multiple triggers change simultaneously, all will be included in the array.
When a webhook subscription is first created, a FIRST_DELIVERY trigger is sent to provide the initial state of the vehicle.
{
  "triggers": [
    {
      "code": "FIRST_DELIVERY"
    }
  ]
}
This initial delivery:
  • Occurs after vehicle subscription to a webhook
  • Contains all subscribed signals at their current values
  • Does not indicate any signal value changes
  • Helps establish baseline state for your application
When FIRST_DELIVERY is sent again:
  • When a vehicle is unsubscribed and then resubscribed to your webhook
  • If your endpoint fails to respond with a 2xx status code, Smartcar will retry delivery with the same FIRST_DELIVERY trigger
Use the triggers field to determine the delivery reason. Check for "code": "FIRST_DELIVERY" to identify initial state deliveries versus change-triggered deliveries.

Payload Structure

eventId
string
required
Unique identifier for this event. Use this for idempotency to prevent duplicate processing.
eventType
string
required
Always "VEHICLE_STATE" for this event type.
vehicleId
string
required
Smartcar vehicle ID for the vehicle this event relates to.
data
object
required
Container for event data.
meta
object
required
Webhook delivery metadata. See Event Reference Overview for complete meta object schema.

Example Payloads

  • Signal Change Trigger
  • First Delivery
Delivered when a configured trigger signal changes value.
{
  "eventId": "550e8400-e29b-41d4-a716-446655440000",
  "eventType": "VEHICLE_STATE",
  "vehicleId": "9af13248-3b73-4c9d-9a4b-d937ce6bc8e2",
  "data": {
    "user": {
      "id": "93b3ea96-ca37-43a9-9073-f4334719iok7"
    },
    "vehicle": {
      "id": "9af13248-3b73-4c9d-9a4b-d937ce6bc8e2",
      "make": "TESLA",
      "model": "Model 3",
      "year": 2020
    },
    "triggers": [
      {
        "code": "tractionbattery-stateofcharge",
        "name": "StateOfCharge",
        "group": "TractionBattery"
      }
    ],
    "signals": [
      {
        "code": "tractionbattery-stateofcharge",
        "name": "StateOfCharge",
        "group": "TractionBattery",
        "body": {
          "unit": "percent",
          "value": 78
        },
        "meta": {
          "oemUpdatedAt": 1731940328000,
          "fetchedAt": 1731940330000
        }
      },
      {
        "code": "charge-ischarging",
        "name": "IsCharging",
        "group": "Charge",
        "body": {
          "value": true
        },
        "meta": {
          "oemUpdatedAt": 1731940328000,
          "fetchedAt": 1731940330000
        }
      },
      {
        "code": "charge-voltage",
        "name": "Voltage",
        "group": "Charge",
        "body": {
          "unit": "volts",
          "value": 240
        },
        "meta": {
          "oemUpdatedAt": 1731940328000,
          "fetchedAt": 1731940330000
        }
      }
    ]
  },
  "meta": {
    "version": "4.0",
    "deliveryId": "48b25f8f-9fea-42e1-9085-81043682cbb8",
    "deliveredAt": 1731940328000,
    "webhookId": "abde94ff-d57d-43b9-8d09-6020db2d977a",
    "webhookName": "Battery Monitoring",
    "signalCount": 3,
    "mode": "LIVE"
  }
}

Signal Reference

The signals array contains data structured according to the Signals Schema Reference. Each signal type has a specific shape:

Signal-Level Errors

Individual signals within a VEHICLE_STATE payload can contain errors if the vehicle doesn’t support that signal or if retrieval fails. When this occurs, the signal will have a status object with error details instead of a body with data:
Signal with Error
{
  "code": "location-isathome",
  "name": "IsAtHome",
  "group": "Location",
  "status": {
    "error": {
      "code": "VEHICLE_NOT_CAPABLE",
      "type": "COMPATIBILITY"
    },
    "value": "ERROR"
  }
}
Partial data delivery: When some signals succeed and others fail, you’ll receive a VEHICLE_STATE event with successful signals containing body data and failed signals containing status errors. This allows you to process available data even when some signals are unavailable.
For complete error handling, see the VEHICLE_ERROR Event documentation.

Processing VEHICLE_STATE Events

Basic Handler

function handleVehicleState(payload) {
  const { vehicleId, data } = payload;
  const { signals, triggers } = data;
  
  // Check if this is first delivery
  const isFirstDelivery = triggers.some(t => t.code === 'FIRST_DELIVERY');
  
  // Process each signal
  signals.forEach(signal => {
    if (signal.body) {
      // Signal has data
      console.log(`${signal.name}: ${JSON.stringify(signal.body)}`);
      updateDatabase(vehicleId, signal.code, signal.body);
    } else if (signal.status) {
      // Signal has error
      console.error(`${signal.name} error: ${signal.status.error.code}`);
      handleSignalError(vehicleId, signal);
    }
  });
}

Best Practices

Always check if you’ve already processed an eventId before updating your database. Retries will have the same eventId but different deliveryId.
async function processWebhook(payload) {
  const { eventId } = payload;
  
  // Check if already processed
  if (await isProcessed(eventId)) {
    console.log(`Already processed ${eventId}`);
    return;
  }
  
  // Process the event
  await handleVehicleState(payload);
  
  // Mark as processed
  await markProcessed(eventId);
}
Use signals[].meta.oemUpdatedAt to determine if incoming data is newer than your stored state. Events can arrive out of order.
function shouldUpdate(currentData, newSignal) {
  if (!currentData) return true;
  
  return newSignal.meta.oemUpdatedAt > currentData.timestamp;
}
Some signals may succeed while others fail. Process available data and log errors for unavailable signals.
signals.forEach(signal => {
  if (signal.body) {
    updateVehicleData(signal);
  } else if (signal.status) {
    logSignalUnavailable(signal);
    // Don't block processing of other signals
  }
});
Use the triggers field to identify initial state deliveries vs. actual signal changes.
const isFirstDelivery = payload.data.triggers.some(
  t => t.code === 'FIRST_DELIVERY'
);

if (isFirstDelivery) {
  // Initialize vehicle state
  initializeVehicle(payload.data);
} else {
  // Update changed signals
  updateVehicleState(payload.data);
}

Next Steps