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

# Webhooks

> Recevez les notifications de résultat de transaction en temps réel sur votre serveur.

## Fonctionnement

Lorsque vous initiez une transaction (collect ou deposit), vous fournissez une `webhookUrl`. Dès que la transaction atteint un état final (`SUCCESS` ou `FAILED`), Remita envoie une requête HTTP POST vers votre URL.

```
[Remita] ──POST──▶ [Votre serveur] ──▶ Traitement du résultat
```

***

## Configuration

Fournissez le champ `webhookUrl` dans le corps de votre requête de transaction :

```json theme={null}
{
  "transferMethod": "OMCM",
  "customerName": "Jean Dupont",
  "externalId": "550e8400-e29b-41d4-a716-446655440000",
  "phoneNumber": "237690000000",
  "amount": 5000,
  "webhookUrl": "https://votre-serveur.com/remita/callback"
}
```

***

## Format du payload reçu

Remita envoie un `POST` avec `Content-Type: application/json` :

```json theme={null}
{
  "externalId": "550e8400-e29b-41d4-a716-446655440000",
  "transactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "SUCCESS",
  "amount": 5000,
  "phoneNumber": "237690000000",
  "transferMethod": "OMCM",
  "message": "Transaction réussie",
  "timestamp": "2026-03-19T10:02:30Z"
}
```

| Champ            | Type   | Description                            |
| ---------------- | ------ | -------------------------------------- |
| `externalId`     | UUID   | Votre identifiant externe              |
| `transactionId`  | String | Identifiant interne Remita             |
| `status`         | String | `SUCCESS` ou `FAILED`                  |
| `amount`         | Number | Montant de la transaction              |
| `phoneNumber`    | String | Numéro de téléphone concerné           |
| `transferMethod` | String | Code opérateur (ex : `OMCM`, `MOMOCM`) |
| `message`        | String | Message descriptif                     |
| `timestamp`      | String | Horodatage ISO 8601                    |

***

## Réponse attendue

<Warning>
  Votre endpoint doit répondre avec un code HTTP `200` dans un délai de **5 secondes**. Toute autre réponse ou timeout entraîne une nouvelle tentative. Déléguez tout traitement lourd à une file de tâches et répondez immédiatement.
</Warning>

***

## Politique de retry

| Tentative | Délai        |
| --------- | ------------ |
| 1ère      | Immédiate    |
| 2ème      | Après 30 s   |
| 3ème      | Après 2 min  |
| 4ème      | Après 10 min |

Après 4 tentatives sans succès, utilisez `POST /api/v1/transaction/chekTransactionStatus?transactionId=<id>` pour forcer la synchronisation avec l'opérateur.

***

## Implémentation de l'endpoint

<CodeGroup>
  ```java Java (Spring Boot) theme={null}
  import org.springframework.web.bind.annotation.*;
  import java.util.Map;

  @RestController
  @RequestMapping("/remita")
  public class RemitaWebhookController {

      @PostMapping("/callback")
      public ResponseEntity<String> handleCallback(@RequestBody Map<String, Object> payload) {
          String externalId = (String) payload.get("externalId");
          String status     = (String) payload.get("status");
          Number amount     = (Number) payload.get("amount");

          if ("SUCCESS".equals(status)) {
              // Créditer le compte client, envoyer une notification...
              System.out.println("Transaction " + externalId + " réussie : " + amount + " XAF");
          } else {
              // Logger l'erreur, notifier l'équipe...
              System.err.println("Transaction " + externalId + " échouée");
          }

          // Toujours répondre 200 rapidement
          return ResponseEntity.ok("OK");
      }
  }
  ```

  ```python Python (FastAPI) theme={null}
  from fastapi import FastAPI, Request
  from fastapi.responses import PlainTextResponse

  app = FastAPI()

  @app.post("/remita/callback")
  async def remita_callback(request: Request):
      payload = await request.json()

      external_id = payload.get("externalId")
      status      = payload.get("status")
      amount      = payload.get("amount", 0)

      if status == "SUCCESS":
          print(f"Transaction {external_id} réussie : {amount} XAF")
          # Créditer le compte, envoyer notification...
      elif status == "FAILED":
          print(f"Transaction {external_id} échouée")
          # Logger, notifier...

      # Toujours répondre 200 dans les 5 secondes
      return PlainTextResponse("OK", status_code=200)
  ```

  ```python Python (Flask) theme={null}
  from flask import Flask, request

  app = Flask(__name__)

  @app.post("/remita/callback")
  def remita_callback():
      payload     = request.get_json()
      external_id = payload.get("externalId")
      status      = payload.get("status")

      if status == "SUCCESS":
          print(f"Transaction {external_id} réussie")
      else:
          print(f"Transaction {external_id} échouée")

      return "OK", 200
  ```

  ```php PHP theme={null}
  <?php
  $payload = json_decode(file_get_contents('php://input'), true);

  if (!$payload) {
      http_response_code(400);
      exit('Invalid payload');
  }

  $externalId = $payload['externalId'] ?? null;
  $status     = $payload['status']     ?? null;
  $amount     = $payload['amount']     ?? 0;

  if ($status === 'SUCCESS') {
      error_log("Transaction $externalId réussie : $amount XAF");
      // Créditer le compte client...
  } elseif ($status === 'FAILED') {
      error_log("Transaction $externalId échouée");
      // Logger, notifier...
  }

  // Toujours répondre 200 rapidement
  http_response_code(200);
  echo 'OK';
  ```

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

  app.post('/remita/callback', (req, res) => {
      const { transactionId, externalId, status, amount } = req.body;

      if (status === 'SUCCESS') {
          console.log(`Paiement reçu — externalId: ${externalId}, montant: ${amount}`);
          // Créditer le compte, envoyer un email...
      } else if (status === 'FAILED') {
          console.error(`Paiement échoué — externalId: ${externalId}`);
      }

      // Toujours répondre 200 dans les 5 secondes
      res.status(200).send('OK');
  });
  ```
</CodeGroup>

***

## Sécurité — Vérifier avant de traiter

<Warning>
  Vérifiez toujours le statut réel via l'API avant d'agir sur un webhook, pour éviter les faux callbacks.
</Warning>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.remita.cm/api/v1/transaction/transaction-status?id=3fa85f64-5717-4562-b3fc-2c963f66afa6" \
    -H "apiKey: YOUR_API_KEY" \
    -H "apiId: YOUR_API_ID" \
    -H "Authorization: Bearer YOUR_TOKEN"
  ```

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

  def verify_and_process(payload: dict, access_token: str, api_key: str, api_id: str):
      transaction_id = payload["transactionId"]

      verified = requests.post(
          "https://api.remita.cm/api/v1/transaction/transaction-status",
          headers={
              "apiKey": api_key,
              "apiId": api_id,
              "Authorization": f"Bearer {access_token}",
          },
          params={"id": transaction_id}
      ).json()

      if verified.get("status") == "SUCCESS":
          print(f"Transaction vérifiée et réussie : {transaction_id}")
      else:
          print(f"Statut non confirmé : {verified.get('status')}")
  ```
</CodeGroup>
