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

# Best Practices

> Build reliable and maintainable webhook integrations following industry standards

Follow these best practices to build a production-ready webhook integration that handles high volume, recovers from failures, and provides a great developer experience.

## Architecture Patterns

### Decouple Receipt from Processing

**Return 200 immediately, process asynchronously.** The most critical pattern for reliable webhook handling is separating acknowledgment from processing.

<Steps>
  <Step title="Receive the webhook">
    Your endpoint receives the POST request from Smartcar
  </Step>

  <Step title="Persist immediately">
    Write the raw payload to a queue, database, or object storage
  </Step>

  <Step title="Return 200">
    Acknowledge receipt with a 200 status code (within 15 seconds)
  </Step>

  <Step title="Process asynchronously">
    A background worker processes the persisted payload
  </Step>
</Steps>

**Why this matters:**

* Prevents timeouts from slow business logic
* Allows you to retry processing without requesting redelivery
* Enables you to update processing logic without losing historical events
* Survives temporary outages in downstream systems

<CodeGroup>
  ```python Python (Flask + Redis Queue) theme={null}
  from flask import Flask, request
  from rq import Queue
  from redis import Redis

  app = Flask(__name__)
  redis_conn = Redis()
  queue = Queue(connection=redis_conn)

  @app.post("/webhooks/smartcar")
  def webhook_handler():
      # 1. Get the raw payload
      payload = request.get_json()
      
      # 2. Queue for processing
      queue.enqueue(process_webhook, payload)
      
      # 3. Return immediately
      return {"status": "received"}, 200

  def process_webhook(payload):
      # This runs asynchronously in a worker
      event_type = payload.get("eventType")
      
      if event_type == "VEHICLE_STATE":
          update_vehicle_state(payload)
      elif event_type == "VEHICLE_ERROR":
          handle_vehicle_error(payload)
  ```

  ```javascript Node.js (Express + AWS SQS) theme={null}
  const express = require('express');
  const AWS = require('aws-sdk');

  const app = express();
  const sqs = new AWS.SQS();

  app.post('/webhooks/smartcar', async (req, res) => {
    try {
      // 1. Get the raw payload
      const payload = req.body;
      
      // 2. Queue for processing
      await sqs.sendMessage({
        QueueUrl: process.env.WEBHOOK_QUEUE_URL,
        MessageBody: JSON.stringify(payload)
      }).promise();
      
      // 3. Return immediately
      res.status(200).json({ status: 'received' });
    } catch (error) {
      console.error('Failed to queue webhook:', error);
      res.status(500).json({ error: 'Internal error' });
    }
  });

  // Separate worker processes the queue
  async function processWebhook(payload) {
    const { eventType } = payload;
    
    if (eventType === 'VEHICLE_STATE') {
      await updateVehicleState(payload);
    } else if (eventType === 'VEHICLE_ERROR') {
      await handleVehicleError(payload);
    }
  }
  ```
</CodeGroup>

<Warning>
  **Don't do this:** If you perform heavy processing before returning a response, your endpoint may timeout and Smartcar will retry, creating duplicate processing work.
</Warning>

***

## Security

### Always Verify Signatures

Every webhook payload includes an `SC-Signature` header containing an HMAC-SHA256 signature. **Always verify this signature** before processing the payload.

```python Python theme={null}
import hmac
import hashlib

def verify_signature(payload, signature, management_token):
    """Verify webhook payload authenticity"""
    expected = hmac.new(
        management_token.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(expected, signature)

@app.post("/webhooks/smartcar")
def webhook_handler():
    # Get signature from header
    signature = request.headers.get('SC-Signature')
    
    # Verify before processing
    if not verify_signature(request.data, signature, MANAGEMENT_TOKEN):
        return {"error": "Invalid signature"}, 401
    
    # Safe to process
    payload = request.get_json()
    # ...
```

**Why this matters:**

* Confirms the payload came from Smartcar
* Prevents spoofed requests from malicious actors
* Protects against replay attacks

See [Payload Verification](/integrations/webhooks/payload-verification) for complete implementation details.

***

## Reliability

### Implement Idempotency

Use the `eventId` field to ensure you process each event exactly once, even if Smartcar retries delivery or you reprocess from your queue.

<CodeGroup>
  ```python Python (with Redis) theme={null}
  import redis

  redis_client = redis.Redis()

  def process_webhook(payload):
      event_id = payload.get("eventId")
      
      # Check if already processed
      if redis_client.exists(f"processed:{event_id}"):
          print(f"Already processed {event_id}, skipping")
          return
      
      # Process the event
      process_vehicle_data(payload)
      
      # Mark as processed (expires after 7 days)
      redis_client.setex(
          f"processed:{event_id}",
          time=604800,  # 7 days
          value="1"
      )
  ```

  ```javascript Node.js (with DynamoDB) theme={null}
  const processWebhook = async (payload) => {
    const { eventId } = payload;
    
    // Check if already processed
    const existing = await dynamodb.get({
      TableName: 'ProcessedEvents',
      Key: { eventId }
    }).promise();
    
    if (existing.Item) {
      console.log(`Already processed ${eventId}, skipping`);
      return;
    }
    
    // Process the event
    await processVehicleData(payload);
    
    // Mark as processed
    await dynamodb.put({
      TableName: 'ProcessedEvents',
      Item: {
        eventId,
        processedAt: new Date().toISOString(),
        ttl: Math.floor(Date.now() / 1000) + 604800 // 7 days
      }
    }).promise();
  };
  ```
</CodeGroup>

**Why this matters:**

* Smartcar retries failed deliveries with the same `eventId`
* Your queue worker might process the same message multiple times
* Prevents duplicate database updates or notifications

***

### Handle Delivery Order

Webhook events are delivered **concurrently** and may arrive out of order. Don't assume events arrive in chronological order.

**Use timestamps to determine freshness:**

```python theme={null}
def update_vehicle_state(payload):
    vehicle_id = payload.get("vehicleId")
    delivered_at = payload["meta"]["deliveredAt"]
    
    # Get current stored state
    current = db.get_vehicle_state(vehicle_id)
    
    # Only update if this event is newer
    if current and current.updated_at > delivered_at:
        print(f"Ignoring older event for {vehicle_id}")
        return
    
    # Safe to update
    db.update_vehicle_state(vehicle_id, payload["data"], delivered_at)
```

**Why this matters:**

* Network delays can cause events to arrive out of sequence
* Retries of older events may arrive after newer events
* Last-write-wins without timestamps can cause stale data

***

## Monitoring & Observability

### Log Everything

Comprehensive logging helps you debug issues, monitor performance, and understand user behavior.

**Key events to log:**

<AccordionGroup>
  <Accordion title="Webhook Receipt" icon="inbox">
    ```json theme={null}
    {
      "timestamp": "2025-01-15T10:30:45Z",
      "event": "webhook.received",
      "eventId": "abc123",
      "eventType": "VEHICLE_STATE",
      "vehicleId": "def456",
      "deliveryId": "ghi789"
    }
    ```
  </Accordion>

  <Accordion title="Signature Verification" icon="shield-check">
    ```json theme={null}
    {
      "timestamp": "2025-01-15T10:30:45Z",
      "event": "signature.verified",
      "eventId": "abc123",
      "valid": true
    }
    ```
  </Accordion>

  <Accordion title="Processing Start/End" icon="play">
    ```json theme={null}
    {
      "timestamp": "2025-01-15T10:30:46Z",
      "event": "processing.started",
      "eventId": "abc123",
      "eventType": "VEHICLE_STATE"
    }
    ```
  </Accordion>

  <Accordion title="Processing Errors" icon="triangle-exclamation">
    ```json theme={null}
    {
      "timestamp": "2025-01-15T10:30:47Z",
      "event": "processing.failed",
      "eventId": "abc123",
      "error": "Database connection timeout",
      "stackTrace": "..."
    }
    ```
  </Accordion>
</AccordionGroup>

### Set Up Alerts

Monitor critical metrics and alert on anomalies:

* **Signature verification failures** - May indicate spoofed requests
* **Processing error rate** - High errors suggest code or infrastructure issues
* **Queue depth** - Growing backlog indicates processing can't keep up
* **Response time** - Approaching 15s timeout threshold
* **Duplicate event processing** - Idempotency checks are catching issues

***

## Error Handling

### Handle VEHICLE\_ERROR Events

Don't ignore `VEHICLE_ERROR` events. They indicate signal retrieval failures and require action.

```python theme={null}
def handle_vehicle_error(payload):
    vehicle_id = payload.get("vehicleId")
    errors = payload["data"].get("errors", [])
    
    for error in errors:
        signal_path = error.get("signalPath")
        error_type = error.get("type")
        message = error.get("message")
        
        if error_type == "VEHICLE_NOT_CAPABLE":
            # Vehicle doesn't support this signal
            db.mark_signal_unsupported(vehicle_id, signal_path)
            
        elif error_type == "PERMISSION_ERROR":
            # Missing permission - notify user to reconnect
            notify_user_reauth(vehicle_id)
            
        elif error_type == "UPSTREAM_ERROR":
            # Temporary OEM issue - will auto-resolve
            log_warning(f"Temporary error for {vehicle_id}: {message}")
```

**Common error types and responses:**

| Error Type            | Meaning                         | Action                                             |
| --------------------- | ------------------------------- | -------------------------------------------------- |
| `VEHICLE_NOT_CAPABLE` | Signal not supported by vehicle | Remove from subscription or mark as N/A            |
| `PERMISSION_ERROR`    | Missing permission scope        | Prompt user to reconnect with required permissions |
| `UPSTREAM_ERROR`      | Temporary OEM failure           | Log and monitor; usually resolves automatically    |
| `RATE_LIMIT`          | Too many requests               | Back off temporarily                               |

See [Event Types](/api-reference/webhooks/event-types#vehicle-error) for complete error reference.

***

### Graceful Degradation

Design your system to continue functioning when webhook processing fails.

**Example strategies:**

<AccordionGroup>
  <Accordion title="Fallback to Polling">
    If webhook processing fails, temporarily fall back to REST API polling for critical data.
  </Accordion>

  <Accordion title="Partial Data Handling">
    If some signals fail, process the successful ones and mark failed signals as unavailable.
  </Accordion>

  <Accordion title="Dead Letter Queue">
    Route persistently failing events to a dead letter queue for manual investigation.
  </Accordion>

  <Accordion title="Circuit Breaker">
    Stop processing webhooks if downstream dependencies are down, return 503 to trigger retries later.
  </Accordion>
</AccordionGroup>

***

## Testing

### Test in Development

Use the Smartcar Dashboard to trigger test webhook deliveries before going to production.

<Steps>
  <Step title="Set up local tunnel">
    Use ngrok or similar to expose your local server:

    ```bash theme={null}
    ngrok http 3000
    ```
  </Step>

  <Step title="Configure webhook">
    Set your ngrok URL as the callback URI in Dashboard
  </Step>

  <Step title="Trigger test events">
    Use the Dashboard to send test `VEHICLE_STATE` and `VEHICLE_ERROR` events
  </Step>

  <Step title="Verify handling">
    Check logs to confirm signature verification, queuing, and processing work correctly
  </Step>
</Steps>

### Validate Edge Cases

Test your integration handles these scenarios:

* **Duplicate deliveries** - Same `eventId` delivered multiple times
* **Out-of-order events** - Older events arriving after newer ones
* **Invalid signatures** - Reject payloads with wrong signature
* **Partial signal failures** - Some signals succeed, others error
* **Large payloads** - Maximum 50KB payload size
* **Missing fields** - Gracefully handle unexpected payload structure

***

## Performance

### Optimize Response Time

Your endpoint must respond within 15 seconds. Optimize for speed:

<CardGroup cols={2}>
  <Card title="Do" icon="check">
    * Return 200 immediately after persisting
    * Use in-memory queues (Redis, SQS)
    * Keep webhook handler logic minimal
    * Use database connection pooling
  </Card>

  <Card title="Don't" icon="xmark">
    * Make API calls before responding
    * Write to slow storage (S3, disk)
    * Perform complex calculations
    * Wait for downstream services
  </Card>
</CardGroup>

### Scale Horizontally

Design for horizontal scaling to handle growing webhook volume:

* **Stateless webhook receivers** - Any instance can handle any request
* **Distributed queues** - SQS, RabbitMQ, Kafka for cross-instance communication
* **Load balancer health checks** - Return 200 on `/health` endpoint
* **Auto-scaling policies** - Scale based on queue depth or CPU

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Delivery Behavior" icon="truck-fast" href="/api-reference/webhooks/delivery-behavior">
    Understand retry policies and technical requirements
  </Card>

  <Card title="Event Types" icon="list" href="/api-reference/webhooks/event-types">
    Complete reference for VEHICLE\_STATE and VEHICLE\_ERROR
  </Card>

  <Card title="Payload Verification" icon="shield-check" href="/integrations/webhooks/payload-verification">
    Implement HMAC signature verification
  </Card>

  <Card title="Webhook Receiver Recipe" icon="book" href="/getting-started/tutorials/webhook-receiver-recipe">
    Deploy production-ready serverless infrastructure
  </Card>
</CardGroup>
