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

# Nutrition AI Asynchronous Processing

> Handle nutrition analysis with asynchronous processing and real-time webhook notifications

## Asynchronous Processing Overview

```mermaid theme={null}
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#ffffff", "primaryTextColor": "#161a1d", "primaryBorderColor": "#ded1e4", "lineColor": "#006ad4", "secondaryColor": "#f2f6f9", "tertiaryColor": "#eef1f4", "background": "#ffffff", "mainBkg": "#ffffff", "secondBkg": "#f2f6f9", "actorBkg": "#eef1f4", "actorTextColor": "#161a1d", "actorLineColor": "#006ad4", "signalColor": "#006ad4", "signalTextColor": "#161a1d", "labelBoxBkgColor": "#ffffff", "labelTextColor": "#4f5356", "loopTextColor": "#4f5356", "noteBkgColor": "#f2f6f9", "noteTextColor": "#4f5356", "activationBkgColor": "#ded1e4", "activationBorderColor": "#9ea1a5"}}}%%
sequenceDiagram
    participant A as App
    participant S as Spike API

    A->>S: POST /auth/hmac<br/>HMAC signature
    activate S
    S-->>A: Access Token
    deactivate S

    A->>S: POST /nutrition_records<br/>Base64 image + token (wait_on_process: false)
    activate S
    activate S
    S-->>A: 200 OK<br/>record_id, status: "pending"
    deactivate S

    S->>+A: POST webhook URL<br/>Complete nutrition results
    deactivate S
    A-->>-S: 200 OK
```

Asynchronous processing allows you to upload food images and receive immediate responses while the AI analysis happens in the background. This approach provides better user experience for interactive applications and enables you to handle multiple requests efficiently.

## How Asynchronous Processing Works

1. **Upload Image** — send a POST request with `wait_on_process: false` (default)
2. **Immediate Response** — receive a response with `status: "pending"` or `status: "processing"`, and a `record_id`
3. **Background Processing** — AI models analyze the image (typically 4–30 seconds)
4. **Get Results** — receive webhook notification with an analysis report when finished

## Getting Results

### Webhooks

Configure a webhook URL to receive real-time notifications when analysis completes:

**Request:**

```json theme={null}
{
  "body": "base64-encoded-image-data",
  "include_ingredients": true,
  "include_nutri_score": true,
  "wait_on_process": false
}
```

**Immediate Response:**

```json theme={null}
{
  "record_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "status": "processing",
  "uploaded_at": "2025-09-15T10:30:04.521Z"
}
```

**Webhook Notification** (when analysis completes):

```json theme={null}
{
  "application_user_id": "end_user_id1",
  "record_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "status": "completed",
  "dish_name": "grilled chicken caesar salad",
  "dish_description": "chicken breast, grilled with romaine lettuce, parmesan cheese, and caesar dressing",
  "nutri_score": "B",
  "serving_size": 275,
  "unit": "g",
  "nutritional_fields": {
    "energy_kcal": 292,
    "protein_g": 42.2,
    "fat_total_g": 10.9,
    "carbohydrate_g": 4.3,
    "fiber_total_dietary_g": 2.1
  },
  "ingredients": [
    {
      "name": "chicken breast, grilled",
      "serving_size": 150.0,
      "unit": "g",
      "nutritional_fields": {
        "energy_kcal": 165,
        "protein_g": 31,
        "fat_total_g": 3.6,
        "carbohydrate_g": 0,
        "fiber_total_dietary_g": 0
      }
    }
  ],
  "uploaded_at": "2025-09-15T10:30:04.521Z",
  "modified_at": "2025-09-15T10:30:12.132Z",
  "consumed_at": "2025-09-15T10:30:04Z"
}
```

### Alternative: Manual Checking

If webhooks are not available in your environment, you can check the status using the [`GET /nutrition_records/{id}`](/api-reference/nutrition-ai-get-nutrition-record) endpoint:

```
GET /nutrition_records/6ba7b810-9dad-11d1-80b4-00c04fd430c8
```

## Webhook Configuration

### Setup

Configure your webhook URL through the [admin console](https://admin.spikeapi.com/). Your endpoint must:

* Respond with HTTP 200 to acknowledge receipt
* Handle POST requests with JSON payloads
* Verify HMAC signatures for security

If requests fail, the system retries **up to 10 times** with exponential backoff.

### Security

All webhook requests include HMAC SHA256 signature verification:

* **Header** — `x-body-signature` contains the hex-encoded HMAC signature
* **Key** — uses your application's webhook signature key (configured in the admin console)
* **Algorithm** — `HMAC-SHA256(webhook_signature_key, request_body)`

See [Webhook Signature](/api-docs/webhooks) for more details.

### Status Handling

Your webhook handler should handle different status values. For complete status definitions, see [Processing Status](/nutrition-ai/implementation#processing-status).

When `status` is `failed`, check the `failure_reason` field for specific details about what prevented successful analysis.

## Best Practices

### 1. Webhook Implementation

* **Respond quickly** — always respond with HTTP 200 immediately, then process data asynchronously
* **Validate signatures** — always verify the HMAC signature before processing webhook data
* **Handle failures gracefully** — check the status field and handle both success and failure cases
* **Implement idempotency** — use the `record_id` to avoid processing the same webhook multiple times

### 2. Error Handling

* Handle webhook delivery failures gracefully
* Store webhook secrets securely and never commit them to version control
* Implement retry logic for critical webhook processing

### 3. User Experience

* Show immediate feedback when the image is uploaded ("Analysis in progress...")
* Provide loading indicators during the processing window
* Handle both success and failure states in your UI

## Use Cases

Asynchronous processing is ideal for:

* **Mobile applications** — immediate feedback while processing happens in the background
* **Web applications** — non-blocking user interfaces with real-time updates
* **High-volume scenarios** — process multiple images concurrently
* **User-facing tools** — food logging apps, dietary tracking applications

## Implementation Examples

<CodeGroup>
  ```go [Go] theme={null}
  package main

  import (
  	"crypto/hmac"
  	"crypto/sha256"
  	"encoding/hex"
  	"encoding/json"
  	"fmt"
  	"io"
  	"net/http"
  	"time"
  )

  const webhookSignatureKey = "YOUR_WEBHOOK_SECRET_FROM_ADMIN_CONSOLE"

  // NutritionRecord represents the webhook payload structure
  type NutritionRecord struct {
  	ApplicationID int64     `json:"application_id"`
  	UID           string    `json:"uid"`
  	RecordID      string    `json:"record_id"`
  	Status        string    `json:"status"`
  	DishName      string    `json:"dish_name"`
  	NutriScore    string    `json:"nutri_score"`
  	UploadedAt    time.Time `json:"uploaded_at"`
  	ModifiedAt    time.Time `json:"modified_at"`
  	// Add other fields as needed
  }

  func main() {
  	http.HandleFunc("/nutrition-webhook", func(w http.ResponseWriter, r *http.Request) {
  		// Verify HMAC signature
  		signature := r.Header.Get("x-body-signature")
  		if signature == "" {
  			http.Error(w, "Missing signature", http.StatusBadRequest)
  			return
  		}

  		// Read the request body
  		body, err := io.ReadAll(r.Body)
  		if err != nil {
  			http.Error(w, "Failed to read body", http.StatusInternalServerError)
  			return
  		}

  		// Calculate and verify HMAC
  		hm := hmac.New(sha256.New, []byte(webhookSignatureKey))
  		hm.Write(body)
  		expectedSignature := hex.EncodeToString(hm.Sum(nil))
  		
  		if signature != expectedSignature {
  			http.Error(w, "Invalid signature", http.StatusUnauthorized)
  			return
  		}

  		// Parse nutrition record
  		var record NutritionRecord
  		if err := json.Unmarshal(body, &record); err != nil {
  			http.Error(w, "Invalid JSON", http.StatusBadRequest)
  			return
  		}

  		// Process the nutrition record
  		fmt.Printf("Received nutrition analysis: %s for user %s\n", record.Status, record.UID)
  		if record.Status == "completed" {
  			fmt.Printf("Dish: %s, Nutri-Score: %s\n", record.DishName, record.NutriScore)
  			// Update your application with the results
  		} else if record.Status == "failed" {
  			fmt.Printf("Analysis failed for record %s\n", record.RecordID)
  			// Handle failure case
  		}

  		// Respond with success
  		w.WriteHeader(http.StatusOK)
  		w.Write([]byte("OK"))
  	})

  	fmt.Println("Starting nutrition webhook server on port 8000")
  	http.ListenAndServe(":8000", nil)
  }
  ```

  ```javascript [JavaScript] theme={null}
  const crypto = require("crypto");
  const express = require("express");

  const WEBHOOK_SIGNATURE_KEY = "YOUR_WEBHOOK_SECRET_FROM_ADMIN_CONSOLE";
  const app = express();

  // Middleware to capture raw body for signature verification
  app.use('/nutrition-webhook', express.raw({type: 'application/json'}));

  app.post('/nutrition-webhook', (req, res) => {
    const signature = req.headers['x-body-signature'];
    
    if (!signature) {
      return res.status(400).send('Missing signature');
    }

    // Verify HMAC signature
    const hmac = crypto.createHmac('sha256', WEBHOOK_SIGNATURE_KEY);
    hmac.update(req.body);
    const expectedSignature = hmac.digest('hex');
    
    if (signature !== expectedSignature) {
      return res.status(401).send('Invalid signature');
    }

    // Parse nutrition record
    try {
      const nutritionRecord = JSON.parse(req.body);
      
      console.log(`Received nutrition analysis: ${nutritionRecord.status} for user ${nutritionRecord.uid}`);
      
      if (nutritionRecord.status === 'completed') {
        console.log(`Dish: ${nutritionRecord.dish_name}, Nutri-Score: ${nutritionRecord.nutri_score}`);
        
        // Update your application with the results
        updateUserNutritionData(nutritionRecord);
        
      } else if (nutritionRecord.status === 'failed') {
        console.log(`Analysis failed: ${nutritionRecord.failure_reason}`);
        
        // Handle failure case
        handleAnalysisFailure(nutritionRecord.record_id, nutritionRecord.uid);
      }
      
      res.status(200).send('OK');
    } catch (error) {
      res.status(400).send('Invalid JSON');
    }
  });

  function updateUserNutritionData(nutritionRecord) {
    // Your application logic to store/process the nutrition data
    console.log('Updating user nutrition data...');
  }

  function handleAnalysisFailure(recordId, uid) {
    // Your application logic to handle failed analysis
    console.log('Handling analysis failure...');
  }

  app.listen(8000, () => {
    console.log('Nutrition webhook server running on port 8000');
  });
  ```

  ```python [Python] theme={null}
  import hmac
  import hashlib
  import json
  from datetime import datetime
  from flask import Flask, request, Response

  app = Flask(__name__)
  WEBHOOK_SIGNATURE_KEY = "YOUR_WEBHOOK_SECRET_FROM_ADMIN_CONSOLE"

  def verify_signature(body: bytes, signature: str) -> bool:
      """Verify the HMAC signature of the request body."""
      expected_signature = hmac.new(
          WEBHOOK_SIGNATURE_KEY.encode('utf-8'),
          body,
          hashlib.sha256
      ).hexdigest()
      return hmac.compare_digest(signature, expected_signature)

  @app.route('/nutrition-webhook', methods=['POST'])
  def handle_nutrition_webhook():
      # Get signature from headers
      signature = request.headers.get('x-body-signature')
      if not signature:
          return Response('Missing signature', status=400)
      
      # Get raw request body
      body = request.get_data()
      
      # Verify signature
      if not verify_signature(body, signature):
          return Response('Invalid signature', status=401)
      
      # Parse nutrition record
      try:
          nutrition_record = json.loads(body)
          
          print(f"Received nutrition analysis: {nutrition_record['status']} for user {nutrition_record['uid']}")
          
          if nutrition_record['status'] == 'completed':
              print(f"Dish: {nutrition_record.get('dish_name', 'Unknown')}")
              print(f"Nutri-Score: {nutrition_record.get('nutri_score', 'N/A')}")
              
              # Update your application with the results
              update_user_nutrition_data(nutrition_record)
              
          elif nutrition_record['status'] == 'failed':
              print(f"Analysis failed: {nutrition_record.get('failure_reason', 'Unknown error')}")
              
              # Handle failure case
              handle_analysis_failure(nutrition_record['record_id'], nutrition_record['uid'])
          
          return Response('OK', status=200)
          
      except json.JSONDecodeError:
          return Response('Invalid JSON', status=400)

  def update_user_nutrition_data(nutrition_record):
      """Update your application with the nutrition analysis results."""
      print("Updating user nutrition data...")
      # Your application logic here

  def handle_analysis_failure(record_id, uid):
      """Handle failed nutrition analysis."""
      print("Handling analysis failure...")
      # Your application logic here

  if __name__ == '__main__':
      print('Starting nutrition webhook server on port 8000')
      app.run(host='0.0.0.0', port=8000)
  ```

  ```php [PHP] theme={null}
  <?php

  class NutritionWebhook {
      private const WEBHOOK_SIGNATURE_KEY = 'YOUR_WEBHOOK_SECRET_FROM_ADMIN_CONSOLE';
      
      public function handleRequest() {
          // Verify HMAC signature
          $signature = $_SERVER['HTTP_X_BODY_SIGNATURE'] ?? '';
          if (empty($signature)) {
              http_response_code(400);
              echo 'Missing signature';
              return;
          }

          // Read request body
          $body = file_get_contents('php://input');
          if ($body === false) {
              http_response_code(500);
              echo 'Failed to read body';
              return;
          }

          // Calculate and verify HMAC
          $expectedSignature = hash_hmac('sha256', $body, self::WEBHOOK_SIGNATURE_KEY);
          if ($signature !== $expectedSignature) {
              http_response_code(401);
              echo 'Invalid signature';
              return;
          }

          // Parse nutrition record
          $nutritionRecord = json_decode($body, true);
          if (json_last_error() !== JSON_ERROR_NONE) {
              http_response_code(400);
              echo 'Invalid JSON';
              return;
          }

          // Process the nutrition record
          echo "Received nutrition analysis: {$nutritionRecord['status']} for user {$nutritionRecord['uid']}\n";
          
          if ($nutritionRecord['status'] === 'completed') {
              $dishName = $nutritionRecord['dish_name'] ?? 'Unknown';
              $nutriScore = $nutritionRecord['nutri_score'] ?? 'N/A';
              echo "Dish: {$dishName}, Nutri-Score: {$nutriScore}\n";
              
              // Update your application with the results
              $this->updateUserNutritionData($nutritionRecord);
              
          } elseif ($nutritionRecord['status'] === 'failed') {
              $failureReason = $nutritionRecord['failure_reason'] ?? 'Unknown error';
              echo "Analysis failed: {$failureReason}\n";
              
              // Handle failure case
              $this->handleAnalysisFailure($nutritionRecord['record_id'], $nutritionRecord['uid']);
          }

          http_response_code(200);
          echo 'OK';
      }
      
      private function updateUserNutritionData($nutritionRecord) {
          // Your application logic to store/process the nutrition data
          echo "Updating user nutrition data...\n";
      }
      
      private function handleAnalysisFailure($recordId, $uid) {
          // Your application logic to handle failed analysis
          echo "Handling analysis failure...\n";
      }
  }

  // Handle the webhook request
  $webhook = new NutritionWebhook();
  $webhook->handleRequest();
  ?>
  ```
</CodeGroup>

For complete API specifications and additional configuration options, see the [Implementation Guide](/nutrition-ai/implementation).
