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

# Webhooks

> Receive real-time event notifications for payments, transfers, and account activity

Webhooks push event notifications to your server the moment something happens — no polling needed. Configure a single HTTPS endpoint to receive all events, then filter by `event.type` in your handler.

<Note>
  All webhook requests from Yativo include an `X-Yativo-Signature` header. Always verify this signature before processing any event.
</Note>

***

## Set Webhook URL

```
POST /business/webhook
```

<ParamField body="url" type="string" required>
  Your HTTPS endpoint that will receive webhook events.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST 'https://api.yativo.com/api/v1/business/webhook' \
    -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
    -H 'Content-Type: application/json' \
    -H 'Idempotency-Key: unique-key-here' \
    -d '{
      "url": "https://your-app.com/webhooks/yativo"
    }'
  ```
</RequestExample>

***

## Get Webhook Configuration

```
GET /business/webhook
```

<RequestExample>
  ```bash cURL theme={null}
  curl -X GET 'https://api.yativo.com/api/v1/business/webhook' \
    -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
  ```
</RequestExample>

***

## Update Webhook URL

```
PUT /business/webhook/{webhook_id}
```

<ParamField path="webhook_id" type="string" required>
  The webhook ID returned when the webhook was created.
</ParamField>

<ParamField body="url" type="string" required>
  New destination URL.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X PUT 'https://api.yativo.com/api/v1/business/webhook/wh-01abc123' \
    -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
    -H 'Content-Type: application/json' \
    -H 'Idempotency-Key: unique-key-here' \
    -d '{
      "url": "https://your-app.com/webhooks/yativo-v2"
    }'
  ```
</RequestExample>

***

## Webhook Event Types

Your endpoint receives `POST` requests with a JSON body containing an `event.type` field.

| Event type                | Triggered when                                        |
| ------------------------- | ----------------------------------------------------- |
| `deposit.created`         | A new deposit is initiated                            |
| `deposit.updated`         | A deposit status changes (e.g. `pending` → `success`) |
| `deposit.completed`       | A deposit is successfully completed                   |
| `payout.updated`          | A payout status changes (e.g. completed or failed)    |
| `payout.completed`        | A payout is successfully completed                    |
| `customer.created`        | A new customer is created                             |
| `customer.kyc.approved`   | A customer's KYC is approved                          |
| `customer.kyc.rejected`   | A customer's KYC is rejected                          |
| `virtual_account.deposit` | A payment arrives at a virtual account                |
| `virtual_account.funded`  | A virtual account receives funds (settled)            |

***

## Event Payloads

### `deposit.created`

```json theme={null}
{
  "event.type": "deposit.created",
  "payload": {
    "id": "93df8440-b756-449c-b0e8-190e2bd8e2bf",
    "amount": 11,
    "gateway": "23",
    "currency": "CLP",
    "status": "pending",
    "deposit_currency": "USD",
    "customer_id": "da44a3e6-eb5d-429f-8d17-357aa5a6cdf2",
    "created_at": "2026-04-01T17:19:56.000000Z"
  }
}
```

### `deposit.updated`

```json theme={null}
{
  "event.type": "deposit.updated",
  "payload": {
    "id": "93df8440-b756-449c-b0e8-190e2bd8e2bf",
    "amount": 11,
    "gateway": "23",
    "currency": "CLP",
    "status": "success",
    "deposit_currency": "USD",
    "receive_amount": 2681,
    "customer_id": "da44a3e6-eb5d-429f-8d17-357aa5a6cdf2",
    "updated_at": "2026-04-01T17:22:10.000000Z"
  }
}
```

### `payout.updated`

```json theme={null}
{
  "event.type": "payout.updated",
  "payload": {
    "payout_id": "4533bb23-0f2d-4c00-8ce3-a2b4ab727b0e",
    "amount": "20.00",
    "currency": "ARS",
    "debit_wallet": "USD",
    "beneficiary_id": 5,
    "status": "completed",
    "created_at": "2026-04-01T21:32:46.000000Z",
    "updated_at": "2026-04-01T21:43:59.000000Z"
  }
}
```

### `customer.created`

```json theme={null}
{
  "event.type": "customer.created",
  "payload": {
    "customer_id": "da44a3e6-eb5d-429f-8d17-357aa5a6cdf2",
    "customer_name": "Jane Doe",
    "customer_email": "jane@example.com",
    "customer_phone": "+5511999999999",
    "customer_country": "BRA",
    "customer_status": "active",
    "customer_kyc_status": "approved",
    "created_at": "2026-04-01T16:15:20.000000Z"
  }
}
```

### `virtual_account.deposit`

```json theme={null}
{
  "event.type": "virtual_account.deposit",
  "payload": {
    "amount": 1000,
    "currency": "BRL",
    "status": "completed",
    "credited_amount": 950,
    "transaction_type": "virtual_account_topup",
    "transaction_id": "TXNMP2HK81BHJ",
    "customer": {
      "customer_id": "da44a3e6-eb5d-429f-8d17-357aa5a6cdf2",
      "customer_name": "Jane Doe",
      "customer_kyc_status": "approved"
    },
    "source": {
      "account_number": "93405934593930",
      "sender_name": "Jane Doe",
      "transaction_fees": 50,
      "amount_received": 1000,
      "credited_amount": 950
    }
  }
}
```

***

## Verifying Webhook Signatures

Every webhook request includes an `X-Yativo-Signature` header containing an HMAC SHA256 hex digest of the raw request body, signed with your webhook secret. **Always verify this before trusting the payload.**

The signature is computed as:

```
HMAC-SHA256(secret, JSON.stringify(payload))  →  hex string
```

Your webhook secret is available in your dashboard under **Developer → Webhooks**.

<Warning>
  Never process a webhook event without first verifying its signature. Skipping this step exposes your endpoint to spoofed events.
</Warning>

### Verification examples

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

  app.post('/webhooks/yativo', express.raw({ type: 'application/json' }), (req, res) => {
    const secret = process.env.YATIVO_WEBHOOK_SECRET;
    const receivedSignature = req.headers['x-yativo-signature'];

    const expectedSignature = crypto
      .createHmac('sha256', secret)
      .update(req.body) // raw Buffer — do NOT parse before this step
      .digest('hex');

    if (!crypto.timingSafeEqual(
      Buffer.from(expectedSignature),
      Buffer.from(receivedSignature)
    )) {
      return res.status(401).send('Invalid signature');
    }

    const event = JSON.parse(req.body);
    // safe to process
    res.status(200).send('OK');
  });
  ```

  ```python Python theme={null}
  import hmac
  import hashlib
  import json
  from flask import Flask, request, abort

  app = Flask(__name__)

  @app.route('/webhooks/yativo', methods=['POST'])
  def webhook():
      secret = os.environ['YATIVO_WEBHOOK_SECRET']
      received_signature = request.headers.get('X-Yativo-Signature', '')

      expected_signature = hmac.new(
          secret.encode(),
          request.data,  # raw bytes — do NOT use request.json here
          hashlib.sha256
      ).hexdigest()

      if not hmac.compare_digest(expected_signature, received_signature):
          abort(401)

      event = request.get_json()
      # safe to process
      return 'OK', 200
  ```

  ```php PHP theme={null}
  <?php
  $secret = getenv('YATIVO_WEBHOOK_SECRET');
  $receivedSignature = $_SERVER['HTTP_X_YATIVO_SIGNATURE'] ?? '';

  $payload = file_get_contents('php://input'); // raw body
  $expectedSignature = hash_hmac('sha256', $payload, $secret);

  if (!hash_equals($expectedSignature, $receivedSignature)) {
      http_response_code(401);
      exit('Invalid signature');
  }

  $event = json_decode($payload, true);
  // safe to process
  ```

  ```go Go theme={null}
  package main

  import (
      "crypto/hmac"
      "crypto/sha256"
      "encoding/hex"
      "io"
      "net/http"
      "os"
  )

  func webhookHandler(w http.ResponseWriter, r *http.Request) {
      secret := []byte(os.Getenv("YATIVO_WEBHOOK_SECRET"))
      receivedSig := r.Header.Get("X-Yativo-Signature")

      body, _ := io.ReadAll(r.Body)

      mac := hmac.New(sha256.New, secret)
      mac.Write(body)
      expectedSig := hex.EncodeToString(mac.Sum(nil))

      if !hmac.Equal([]byte(expectedSig), []byte(receivedSig)) {
          http.Error(w, "Invalid signature", http.StatusUnauthorized)
          return
      }

      // safe to process body
      w.WriteHeader(http.StatusOK)
  }
  ```

  ```java Java theme={null}
  import javax.crypto.Mac;
  import javax.crypto.spec.SecretKeySpec;
  import java.security.MessageDigest;

  @PostMapping("/webhooks/yativo")
  public ResponseEntity<String> webhook(
      @RequestHeader("X-Yativo-Signature") String receivedSignature,
      @RequestBody byte[] rawBody
  ) throws Exception {
      String secret = System.getenv("YATIVO_WEBHOOK_SECRET");

      Mac mac = Mac.getInstance("HmacSHA256");
      mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
      byte[] rawHmac = mac.doFinal(rawBody);

      StringBuilder hex = new StringBuilder();
      for (byte b : rawHmac) hex.append(String.format("%02x", b));
      String expectedSignature = hex.toString();

      // Constant-time comparison
      if (!MessageDigest.isEqual(
          expectedSignature.getBytes(),
          receivedSignature.getBytes()
      )) {
          return ResponseEntity.status(401).body("Invalid signature");
      }

      // safe to process
      return ResponseEntity.ok("OK");
  }
  ```

  ```ruby Ruby theme={null}
  require 'openssl'

  post '/webhooks/yativo' do
    secret = ENV['YATIVO_WEBHOOK_SECRET']
    received_signature = request.env['HTTP_X_YATIVO_SIGNATURE']

    payload = request.body.read
    expected_signature = OpenSSL::HMAC.hexdigest('SHA256', secret, payload)

    unless Rack::Utils.secure_compare(expected_signature, received_signature)
      halt 401, 'Invalid signature'
    end

    event = JSON.parse(payload)
    # safe to process
    status 200
  end
  ```

  ```csharp C# (.NET) theme={null}
  [HttpPost("/webhooks/yativo")]
  public async Task<IActionResult> Webhook()
  {
      var secret = Environment.GetEnvironmentVariable("YATIVO_WEBHOOK_SECRET");
      var receivedSignature = Request.Headers["X-Yativo-Signature"].ToString();

      using var reader = new StreamReader(Request.Body);
      var rawBody = await reader.ReadToEndAsync();

      using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
      var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody));
      var expectedSignature = BitConverter.ToString(hash).Replace("-", "").ToLower();

      // Constant-time comparison
      if (!CryptographicOperations.FixedTimeEquals(
          Encoding.UTF8.GetBytes(expectedSignature),
          Encoding.UTF8.GetBytes(receivedSignature)
      )) return Unauthorized("Invalid signature");

      // safe to process
      return Ok();
  }
  ```
</CodeGroup>

<Warning>
  Always use **constant-time comparison** (`crypto.timingSafeEqual`, `hmac.compare_digest`, `hash_equals`, etc.) — never a plain `===` or `==`. Regular string comparison is vulnerable to timing attacks.
</Warning>

<Note>
  Use the **raw request body bytes** to compute the signature — not a re-serialized version of the parsed JSON. Parsing and re-stringifying can change whitespace or key order, causing signature mismatches.
</Note>

***

## Webhook Handler Example

```javascript Node.js (Express) theme={null}
app.post('/webhooks/yativo', express.raw({ type: 'application/json' }), (req, res) => {
  // 1. Verify signature first (see above)
  verifySignature(req);

  const event = JSON.parse(req.body);

  switch (event['event.type']) {
    case 'deposit.updated':
      if (event.payload.status === 'success') {
        creditCustomer(event.payload.customer_id, event.payload.receive_amount);
      }
      break;

    case 'payout.updated':
      updatePayoutStatus(event.payload.payout_id, event.payload.status);
      break;

    case 'virtual_account.deposit':
      notifyCustomer(event.payload.customer.customer_id, event.payload.credited_amount);
      break;

    case 'customer.kyc.approved':
      enableCustomerFeatures(event.payload.customer_id);
      break;
  }

  res.status(200).send('OK');
});
```

<Note>
  Respond with a `2xx` status within **10 seconds** to acknowledge receipt. Failed deliveries are retried automatically.
</Note>

***

## Event Log

Retrieve all webhook events sent to your endpoint:

```
GET /business/events/all
```

<ParamField query="per_page" type="number">
  Results per page.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X GET 'https://api.yativo.com/api/v1/business/events/all?per_page=20' \
    -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
  ```
</RequestExample>

### Get Single Event

```
GET /business/events/show/{id}
```

```bash theme={null}
curl -X GET 'https://api.yativo.com/api/v1/business/events/show/evt_01HX9KZMB3F7VNQP8R2WDGT4E5' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
```

***

## API Request Logs

Audit all API calls made against your account:

```
GET /business/logs/all
```

<ParamField query="status" type="string">
  Filter by HTTP status code (e.g. `200`, `400`).
</ParamField>

<ParamField query="method" type="string">
  Filter by HTTP method (`GET`, `POST`, `PUT`, `DELETE`).
</ParamField>

<ParamField query="page" type="number">
  Page number.
</ParamField>

<ParamField query="per_page" type="number">
  Results per page.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X GET 'https://api.yativo.com/api/v1/business/logs/all?method=POST&per_page=20' \
    -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
  ```
</RequestExample>
