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

# Configure Webhooks

> Set up real-time notifications for payment status updates

Carbn webhooks allow you to receive real-time notifications when payment events occur in your Carbn account. This guide covers registering, implementing, and enabling webhooks using Carbn's REST API.

## Prerequisites

* A Carbn account with API access
* Carbn API credentials (API key)
* HTTPS endpoint with valid certificate
* Development environment with your preferred language

***

## Step 1: Create Your Webhook Endpoint

First, create an endpoint on your server to receive webhook notifications.

### Webhook Payload Structure

All webhook payloads follow this structure:

<CodeGroup>
  ```json Response theme={null}
  {
    "status": "PAYMENT_PROCESSED",
    "txn_id": "da4ac399-ed76-42f3-ba29-e4f4b7e2b46c",
    "user_id": "38758d6e-05f7-46a2-86d1-5ab45a49bc64"
  }
  ```
</CodeGroup>

### HTTP Headers

When Carbn sends webhook events, your endpoint will receive these headers:

* `Content-Type: application/json`
* `User-Agent: Java/17.0.14`
* `X-Webhook-Event-Id`: Unique identifier for this delivery attempt
* `X-Webhook-Id`: Identifier of the webhook configuration
* `X-Webhook-Signature`: Base64 encoded signature for payload verification

### Example Webhook Handler

<CodeGroup>
  ```javascript Node.js/Express theme={null}
  const express = require('express');
  const crypto = require('crypto');
  const app = express();

  app.use(express.json());

  app.post('/webhooks/carbn', (req, res) => {
    const signature = req.headers['x-webhook-signature'];
    const eventId = req.headers['x-webhook-event-id'];
    const webhookId = req.headers['x-webhook-id'];
    const payload = req.body;
    
    // Verify webhook signature (recommended)
    if (!verifySignature(JSON.stringify(payload), signature)) {
      return res.status(401).send('Unauthorized');
    }
    
    // Handle idempotency using event ID
    if (isEventProcessed(eventId)) {
      return res.status(200).send('Already processed');
    }
    
    const { status, txn_id, user_id } = payload;
    
    switch (status) {
      case 'PAYMENT_PROCESSED':
        handlePaymentProcessed(txn_id, user_id);
        break;
      case 'PAYMENT_FAILED':
        handlePaymentFailed(txn_id, user_id);
        break;
      default:
        console.log('Unknown payment status:', status);
    }
    
    // Mark event as processed
    markEventProcessed(eventId);
    res.status(200).send('OK');
  });

  function verifySignature(payload, signature) {
    // Carbn uses RSA signature verification with base64 encoded signature
    // This is a simplified example - implement proper RSA verification
    const publicKey = process.env.CARBN_PUBLIC_KEY;
    
    try {
      const verify = crypto.createVerify('SHA256');
      verify.update(payload);
      const signatureBuffer = Buffer.from(signature, 'base64');
      return verify.verify(publicKey, signatureBuffer);
    } catch (error) {
      console.error('Signature verification failed:', error);
      return false;
    }
  }

  function handlePaymentProcessed(txnId, userId) {
    console.log(`Payment ${txnId} processed for user ${userId}`);
    // Update your database, notify user, etc.
  }

  function handlePaymentFailed(txnId, userId) {
    console.log(`Payment ${txnId} failed for user ${userId}`);
    // Handle payment failure, notify user, etc.
  }
  ```

  ```python Python/Flask theme={null}
  from flask import Flask, request, jsonify
  import base64
  import json
  from cryptography.hazmat.primitives import hashes
  from cryptography.hazmat.primitives.asymmetric import padding
  from cryptography.hazmat.primitives import serialization

  app = Flask(__name__)
  processed_events = set()

  @app.route('/webhooks/carbn', methods=['POST'])
  def handle_webhook():
      signature = request.headers.get('X-Webhook-Signature')
      event_id = request.headers.get('X-Webhook-Event-Id')
      webhook_id = request.headers.get('X-Webhook-Id')
      
      payload = request.get_data()
      
      # Verify webhook signature (recommended)
      if not verify_signature(payload, signature):
          return jsonify({'error': 'Unauthorized'}), 401
      
      # Handle idempotency
      if event_id in processed_events:
          return jsonify({'status': 'already processed'}), 200
      
      data = request.json
      status = data.get('status')
      txn_id = data.get('txn_id')
      user_id = data.get('user_id')
      
      if status == 'PAYMENT_PROCESSED':
          handle_payment_processed(txn_id, user_id)
      elif status == 'PAYMENT_FAILED':
          handle_payment_failed(txn_id, user_id)
      else:
          print(f'Unknown payment status: {status}')
      
      # Mark event as processed
      processed_events.add(event_id)
      return jsonify({'status': 'success'}), 200

  def verify_signature(payload, signature):
      try:
          # Load your Carbn public key
          public_key_pem = os.environ['CARBN_PUBLIC_KEY']
          public_key = serialization.load_pem_public_key(public_key_pem.encode())
          
          # Decode the base64 signature
          signature_bytes = base64.b64decode(signature)
          
          # Verify the signature
          public_key.verify(
              signature_bytes,
              payload,
              padding.PKCS1v15(),
              hashes.SHA256()
          )
          return True
      except Exception as e:
          print(f'Signature verification failed: {e}')
          return False

  def handle_payment_processed(txn_id, user_id):
      print(f"Payment {txn_id} processed for user {user_id}")
      # Update your database, notify user, etc.

  def handle_payment_failed(txn_id, user_id):
      print(f"Payment {txn_id} failed for user {user_id}")
      # Handle payment failure, notify user, etc.
  ```
</CodeGroup>

***

## Step 2: Register Your Webhook

Once your endpoint is ready, register it with Carbn. You must specify both `url` and `event_category`. **Important:** Webhooks are created with `status: "disabled"` by default and must be enabled separately.

### Webhook event categories

Choose one event category per webhook. Each webhook receives only events for its category:

| Event category               | Description                                                                                          |
| ---------------------------- | ---------------------------------------------------------------------------------------------------- |
| `ONRAMP_TRANSACTION_STATUS`  | Onramp (fiat-to-crypto) transfer status updates (e.g. initiated, funds received, payment processed). |
| `OFFRAMP_TRANSACTION_STATUS` | Offramp (crypto-to-fiat) transfer status updates.                                                    |
| `PAYOUT`                     | Payout status updates for local currency payouts (e.g. INITIATED, PAYOUT\_PROCESSED, FAILED).        |
| `USER_STATUS_UPDATE`         | User KYC and onboarding status changes (e.g. under\_review, active, rejected, incomplete).           |
| `CUSTODY_WALLET_TRANSACTION` | Custody wallet transfer status updates (e.g. initiated, in\_review, completed).                      |

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.carbnconnect.com/onboarding/api/v1/webhooks \
    --header 'Content-Type: application/json' \
    --header 'x-api-key: <api-key>' \
    --data '{
      "url": "https://your-app.com/webhooks/carbn",
      "event_category": "ONRAMP_TRANSACTION_STATUS"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.carbnconnect.com/onboarding/api/v1/webhooks', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': '<api-key>'
    },
    body: JSON.stringify({
      url: "https://your-app.com/webhooks/carbn",
      event_category: "ONRAMP_TRANSACTION_STATUS"
    })
  });

  const webhook = await response.json();
  console.log('Webhook registered:', webhook.id);
  ```

  ```python Python theme={null}
  import requests

  headers = {
      'Content-Type': 'application/json',
      'x-api-key': '<api-key>'
  }

  data = {
      "url": "https://your-app.com/webhooks/carbn",
      "event_category": "ONRAMP_TRANSACTION_STATUS"
  }

  response = requests.post(
      'https://api.carbnconnect.com/onboarding/api/v1/webhooks',
      headers=headers,
      json=data
  )

  webhook = response.json()
  print(f"Webhook registered: {webhook['id']}")
  ```
</CodeGroup>

**Response:**

<CodeGroup>
  ```json Response theme={null}
  {
    "id": "f79bc473-d45d-4026-86ec-864d4d047905",
    "url": "https://your-app.com/webhooks/carbn",
    "status": "disabled",
    "created_at": "2024-01-15T10:30:00Z"
  }
  ```
</CodeGroup>

Save the `webhook_id` from the response - you'll need it to enable the webhook.

***

## Step 3: Enable Your Webhook

Before your webhook can receive events, you must enable it using the PUT endpoint:

<CodeGroup>
  ```bash cURL theme={null}
  curl --request PUT \
    --url https://api.carbnconnect.com/onboarding/api/v1/webhooks/{webhook_id} \
    --header 'Content-Type: application/json' \
    --header 'x-api-key: <api-key>' \
    --data '{
      "url": "https://your-app.com/webhooks/carbn",
      "status": "enabled"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(`https://api.carbnconnect.com/onboarding/api/v1/webhooks/${webhookId}`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': '<api-key>'
    },
    body: JSON.stringify({
      url: "https://your-app.com/webhooks/carbn",
      status: "enabled"
    })
  });

  const webhook = await response.json();
  console.log('Webhook enabled:', webhook.status);
  ```

  ```python Python theme={null}
  import requests

  headers = {
      'Content-Type': 'application/json',
      'x-api-key': '<api-key>'
  }

  data = {
      "url": "https://your-app.com/webhooks/carbn",
      "status": "enabled"
  }

  response = requests.put(
      f'https://api.carbnconnect.com/onboarding/api/v1/webhooks/{webhook_id}',
      headers=headers,
      json=data
  )

  webhook = response.json()
  print(f"Webhook status: {webhook['status']}")
  ```
</CodeGroup>

***

## Payment Status Events

Carbn webhooks notify you when payment statuses change. The main status values are:

* **`PAYMENT_PROCESSED`** - Payment has been successfully processed
* **`PAYMENT_FAILED`** - Payment processing failed

**Example payload:**

<CodeGroup>
  ```json Response theme={null}
  {
    "status": "PAYMENT_PROCESSED",
    "txn_id": "da4ac399-ed76-42f3-ba29-e4f4b7e2b46c",
    "user_id": "38758d6e-05f7-46a2-86d1-5ab45a49bc64"
  }
  ```
</CodeGroup>

***

## What's Next?

<CardGroup cols={1}>
  <Card title="Webhook Verification" icon="shield-check" href="/documentation/developer-platform/monitoring/webhook-verification" arrow="true" horizontal>
    Learn how to verify webhook authenticity using RSA signatures
  </Card>
</CardGroup>

Your webhook is now configured to receive real-time payment notifications from Carbn. Next, implement proper signature verification to ensure webhook security.
