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

# Balance Check Run Completed

> Fires when a balance check run completes

# Balance Check Run Completed Webhook

Fires when a balance check run completes. A notification will be sent to the `webhook_url` specified in the `/v1/balance_requests` endpoint.

<Warning>
  **Important Considerations:**

  * Webhook delivery is not guaranteed
  * Webhooks will be retried for up to 30 minutes with exponential backoff
  * If delivery fails after 30 minutes, no further retries will occur
  * Always implement polling as a fallback mechanism
</Warning>

<Note>
  If your webhook endpoint restricts inbound traffic by IP, make sure to allowlist Rightfoot's outbound IPs. See [IP Allowlisting](/api-reference/webhooks/ip-allowlist).
</Note>

## Webhook Response

Your webhook endpoint should respond with an HTTP `2xx` status code to confirm receipt:

```json theme={null}
{
  "status": "received"
}
```

<Note>
  If a `2xx` response is not received, the webhook will be retried with exponential backoff for up to 30 minutes.
</Note>

## Implementing a Webhook Endpoint

Here are examples of implementing a webhook endpoint in various languages:

<CodeGroup>
  ```python Python (Flask) theme={null}
  from flask import Flask, request, jsonify
  import logging

  app = Flask(__name__)

  @app.route('/webhook/balance-batch-complete', methods=['POST'])
  def handle_balance_webhook():
      try:
          # Parse the webhook payload
          data = request.get_json()
          
          # Validate required fields
          if not all(k in data for k in ['event_uuid', 'type', 'batch_id']):
              return jsonify({'error': 'Missing required fields'}), 400
          
          # Verify the event type
          if data['type'] != 'BALANCE_BATCH_COMPLETED':
              return jsonify({'error': 'Unexpected event type'}), 400
          
          # Log the event
          logging.info(f"Balance batch completed: {data['batch_id']}")
          
          # Process the completed batch (fetch results, update database, etc.)
          process_completed_batch(data['batch_id'])
          
          # Return success response
          return jsonify({'status': 'received'}), 200
          
      except Exception as e:
          logging.error(f"Webhook processing error: {str(e)}")
          return jsonify({'error': 'Internal server error'}), 500

  def process_completed_batch(batch_id):
      # Fetch balance results using the batch_id
      # Update your database
      # Trigger any downstream processes
      pass
  ```

  ```javascript Node.js (Express) theme={null}
  const express = require('express');
  const app = express();

  app.use(express.json());

  app.post('/webhook/balance-batch-complete', async (req, res) => {
    try {
      const { event_uuid, type, batch_id } = req.body;
      
      // Validate required fields
      if (!event_uuid || !type || !batch_id) {
        return res.status(400).json({ error: 'Missing required fields' });
      }
      
      // Verify the event type
      if (type !== 'BALANCE_BATCH_COMPLETED') {
        return res.status(400).json({ error: 'Unexpected event type' });
      }
      
      // Log the event
      console.log(`Balance batch completed: ${batch_id}`);
      
      // Process the completed batch
      await processCompletedBatch(batch_id);
      
      // Return success response
      res.status(200).json({ status: 'received' });
      
    } catch (error) {
      console.error('Webhook processing error:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  });

  async function processCompletedBatch(batchId) {
    // Fetch balance results using the batchId
    // Update your database
    // Trigger any downstream processes
  }

  app.listen(3000, () => {
    console.log('Webhook server listening on port 3000');
  });
  ```

  ```php PHP theme={null}
  <?php
  header('Content-Type: application/json');

  // Get the webhook payload
  $payload = file_get_contents('php://input');
  $data = json_decode($payload, true);

  // Validate JSON parsing
  if (json_last_error() !== JSON_ERROR_NONE) {
      http_response_code(400);
      echo json_encode(['error' => 'Invalid JSON payload']);
      exit;
  }

  // Validate required fields
  if (!isset($data['event_uuid']) || !isset($data['type']) || !isset($data['batch_id'])) {
      http_response_code(400);
      echo json_encode(['error' => 'Missing required fields']);
      exit;
  }

  // Verify the event type
  if ($data['type'] !== 'BALANCE_BATCH_COMPLETED') {
      http_response_code(400);
      echo json_encode(['error' => 'Unexpected event type']);
      exit;
  }

  // Log the event
  error_log("Balance batch completed: " . $data['batch_id']);

  try {
      // Process the completed batch
      processCompletedBatch($data['batch_id']);
      
      // Return success response
      http_response_code(200);
      echo json_encode(['status' => 'received']);
      
  } catch (Exception $e) {
      error_log("Webhook processing error: " . $e->getMessage());
      http_response_code(500);
      echo json_encode(['error' => 'Internal server error']);
  }

  function processCompletedBatch($batchId) {
      // Fetch balance results using the batchId
      // Update your database
      // Trigger any downstream processes
  }
  ?>
  ```
</CodeGroup>

## Retry Mechanism

The webhook delivery system implements exponential backoff:

1. **Initial attempt** - Immediate
2. **First retry** - After 1 minute
3. **Second retry** - After 2 minutes
4. **Third retry** - After 4 minutes
5. **Subsequent retries** - Doubling interval up to 30 minutes total

After 30 minutes, no further retry attempts will be made.

## Security Considerations

To secure your webhook endpoint:

1. **Use HTTPS** - Always use SSL/TLS encryption for your webhook endpoint
2. **Validate payloads** - Check that all required fields are present
3. **Implement idempotency** - Handle duplicate webhook deliveries gracefully
4. **Add authentication** - Consider implementing webhook signatures or API keys
5. **Rate limiting** - Protect against potential abuse

## Fallback Strategy

Since webhook delivery is not guaranteed, implement a polling fallback:

<CodeGroup>
  ```python Python theme={null}
  import time
  import requests

  def wait_for_batch_completion(batch_id, api_key, max_wait=3600):
      """
      Poll for batch completion with exponential backoff
      """
      start_time = time.time()
      poll_interval = 30  # Start with 30 seconds
      
      while time.time() - start_time < max_wait:
          # Check if batch is complete
          response = requests.get(
              f"https://api.rightfoot.com/v1/balances?batchId={batch_id}&limit=1",
              headers={"Authorization": f"Bearer {api_key}"}
          )
          
          if response.status_code == 200:
              # Batch is complete
              return True
          elif response.status_code == 404:
              # Batch still processing
              time.sleep(poll_interval)
              poll_interval = min(poll_interval * 1.5, 300)  # Cap at 5 minutes
          else:
              # Handle error
              raise Exception(f"Error checking batch status: {response.status_code}")
      
      return False  # Timeout reached
  ```

  ```javascript JavaScript theme={null}
  async function waitForBatchCompletion(batchId, apiKey, maxWait = 3600) {
    const startTime = Date.now();
    let pollInterval = 30000; // Start with 30 seconds
    
    while ((Date.now() - startTime) / 1000 < maxWait) {
      try {
        const response = await fetch(
          `https://api.rightfoot.com/v1/balances?batchId=${batchId}&limit=1`,
          {
            headers: {
              'Authorization': `Bearer ${apiKey}`
            }
          }
        );
        
        if (response.status === 200) {
          // Batch is complete
          return true;
        } else if (response.status === 404) {
          // Batch still processing
          await new Promise(resolve => setTimeout(resolve, pollInterval));
          pollInterval = Math.min(pollInterval * 1.5, 300000); // Cap at 5 minutes
        } else {
          // Handle error
          throw new Error(`Error checking batch status: ${response.status}`);
        }
      } catch (error) {
        throw error;
      }
    }
    
    return false; // Timeout reached
  }
  ```
</CodeGroup>

## Best Practices

1. **Always implement polling** - Don't rely solely on webhooks
2. **Handle duplicates** - Your system should be idempotent
3. **Log all events** - Keep an audit trail of webhook receipts
4. **Monitor failures** - Set up alerts for webhook processing errors
5. **Process asynchronously** - Return `200` quickly and process in the background


## OpenAPI

````yaml WEBHOOK balance_check.run.completed
openapi: 3.1.0
info:
  title: Rightfoot API
  description: >-
    Submit a batch of authorizers for balance checks, retrieve processed
    balances, and check the status of a batch.
  version: 0.3.0-alpha
servers:
  - url: https://api.rightfoot.com
security:
  - BearerAuth: []
tags:
  - name: General Availability
    description: |
      **General Availability** indicates that the endpoint is production-ready.
  - name: Early Access
    description: >
      **Early Access** indicates that the endpoint is available for early access
      customers and is being actively refined.
  - name: In Development
    description: >
      **In Development** indicates that the endpoint is currently being built
      and will be available in an upcoming release.
paths: {}
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: >
        Authentication to the API is performed via Bearer Token Authentication.
        Provide your API key as the bearer token in the Authorization header.


        All API requests must be made over HTTPS. Calls made over plain HTTP
        will fail. API requests without authentication will also fail.

````