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

# KYC Expiration Warning

> Webhook event fired when a KYC verification is close to expiration

<Info>
  **Event Name:** `kyc_expiration_warning`
</Info>

<Note>
  This event is emitted when an active KYC verification is approaching its document expiration date. It is intended to give merchants enough time to trigger a new KYC verification session before the current verification expires.
</Note>

## Event Payload

The webhook payload contains the following structure:

<CodeGroup>
  ```json Example Payload (User) theme={null}
  {
    "eventId": "550e8400-e29b-41d4-a716-446655440000",
    "eventType": "kyc_expiration_warning",
    "timestamp": "2025-06-19T17:14:13.123Z",
    "data": {
      "kycId": 12345,
      "entityType": "user",
      "userId": "550e8400-e29b-41d4-a716-446655440001",
      "expirationDate": "2025-07-03",
      "daysRemaining": 14,
      "action": "Create a new KYC verification session for the affected user before their current verification expires."
    }
  }
  ```

  ```json Example Payload (Related Party) theme={null}
  {
    "eventId": "550e8400-e29b-41d4-a716-446655440000",
    "eventType": "kyc_expiration_warning",
    "timestamp": "2025-06-19T17:14:13.123Z",
    "data": {
      "kycId": 12345,
      "entityType": "related-party",
      "relatedPartyId": "550e8400-e29b-41d4-a716-446655440003",
      "expirationDate": "2025-07-03",
      "daysRemaining": 14,
      "action": "Create a new KYC verification session for the affected user before their current verification expires."
    }
  }
  ```
</CodeGroup>

## Field Description

<ParamField path="eventId" type="string" required>
  Unique event identifier (UUID)
</ParamField>

<ParamField path="eventType" type="string" required>
  Event type - always `kyc_expiration_warning` for this webhook
</ParamField>

<ParamField path="timestamp" type="string" required>
  Event timestamp in ISO 8601 format
</ParamField>

<ParamField path="data" type="object" required>
  Contains the event data
</ParamField>

<ParamField path="data.kycId" type="integer" required>
  Unique KYC record identifier
</ParamField>

<ParamField path="data.entityType" type="string" required>
  Entity type associated with the KYC record. Possible values: `user`, `related-party`
</ParamField>

<ParamField path="data.userId" type="string">
  Unique user ID (UUID)
</ParamField>

<ParamField path="data.relatedPartyId" type="string">
  Unique related party ID (UUID)
</ParamField>

<ParamField path="data.expirationDate" type="string" required>
  Document expiration date in `YYYY-MM-DD` format
</ParamField>

<ParamField path="data.daysRemaining" type="integer" required>
  Number of days remaining before the KYC document expires
</ParamField>

<ParamField path="data.action" type="string" required>
  Recommended action for the merchant to prevent the KYC from expiring
</ParamField>

## When This Event Is Sent

This webhook is sent when a KYC record is close to expiration and has not been previously notified.

Typical conditions include:

* The KYC is still in a valid operational state
* The document expiration date is within the warning threshold
* A warning has not already been sent for that KYC

In the current implementation, the warning threshold is **14 days before expiration**.

## Expected Responses

<CardGroup cols={3}>
  <Card title="Success Response" icon="circle-check" color="#16a34a">
    **HTTP 200 OK**

    The webhook was processed successfully.
  </Card>

  <Card title="Client Error" icon="triangle-exclamation" color="#ea580c">
    **HTTP 4xx Status**

    Client-side error. Will not be retried.
  </Card>

  <Card title="Server Error" icon="circle-xmark" color="#dc2626">
    **HTTP 5xx Status**

    Server-side error. Will be retried with exponential backoff.
  </Card>
</CardGroup>

## Implementation Example

<CodeGroup>
  ```javascript Node.js theme={null}
  app.post('/webhooks/ralio', async (req, res) => {
    const { eventType, data } = req.body;

    if (eventType === 'kyc_expiration_warning') {
      const { kycId, entityType, userId, relatedPartyId, expirationDate, daysRemaining, action } = data;
      const subjectId = userId ?? relatedPartyId;

      console.log(
        `KYC ${kycId} for ${entityType} ${subjectId} expires on ${expirationDate} (${daysRemaining} days remaining)`
      );

      if (entityType === 'user' && userId) {
        await createRenewalSessionForUser(userId);
      }

      if (entityType === 'related-party' && relatedPartyId) {
        await createRenewalSessionForRelatedParty(relatedPartyId);
      }

      console.log(`Recommended action: ${action}`);

      res.status(200).json({ received: true });
    }
  });
  ```

  ```python Python theme={null}
  @app.route('/webhooks/ralio', methods=['POST'])
  def handle_webhook():
      payload = request.get_json()

      if payload['eventType'] == 'kyc_expiration_warning':
          data = payload['data']

          kyc_id = data['kycId']
          entity_type = data['entityType']
          user_id = data.get('userId')
          related_party_id = data.get('relatedPartyId')
          expiration_date = data['expirationDate']
          days_remaining = data['daysRemaining']
          action = data['action']
          subject_id = user_id or related_party_id

          print(
              f"KYC {kyc_id} for {entity_type} {subject_id} expires on {expiration_date} "
              f"({days_remaining} days remaining)"
          )

          if entity_type == 'user' and user_id:
              create_renewal_session_for_user(user_id)

          if entity_type == 'related-party' and related_party_id:
              create_renewal_session_for_related_party(related_party_id)

          print(f"Recommended action: {action}")

          return jsonify({'received': True}), 200
  ```

  ```go Go theme={null}
  func handleWebhook(w http.ResponseWriter, r *http.Request) {
      var payload struct {
          EventType string `json:"eventType"`
          Data  struct {
              KycID          int    `json:"kycId"`
              EntityType     string `json:"entityType"`
              UserID         string `json:"userId"`
              RelatedPartyID string `json:"relatedPartyId"`
              ExpirationDate string `json:"expirationDate"`
              DaysRemaining  int    `json:"daysRemaining"`
              Action         string `json:"action"`
          } `json:"data"`
      }

      if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
          http.Error(w, "Invalid JSON", http.StatusBadRequest)
          return
      }

      if payload.EventType == "kyc_expiration_warning" {
          subjectID := payload.Data.UserID
          if subjectID == "" {
              subjectID = payload.Data.RelatedPartyID
          }

          log.Printf(
              "KYC %d for %s %s expires on %s (%d days remaining)",
              payload.Data.KycID,
              payload.Data.EntityType,
              subjectID,
              payload.Data.ExpirationDate,
              payload.Data.DaysRemaining,
          )

          if payload.Data.EntityType == "user" && payload.Data.UserID != "" {
              if err := createRenewalSessionForUser(payload.Data.UserID); err != nil {
                  http.Error(w, "Internal error", http.StatusInternalServerError)
                  return
              }
          }

          if payload.Data.EntityType == "related-party" && payload.Data.RelatedPartyID != "" {
              if err := createRenewalSessionForRelatedParty(payload.Data.RelatedPartyID); err != nil {
                  http.Error(w, "Internal error", http.StatusInternalServerError)
                  return
              }
          }

          log.Printf("Recommended action: %s", payload.Data.Action)

          w.Header().Set("Content-Type", "application/json")
          w.WriteHeader(http.StatusOK)
          json.NewEncoder(w).Encode(map[string]bool{"received": true})
      }
  }
  ```
</CodeGroup>
