Politique de retry
3 retries automatiques : immédiat, +30 s, +5 min. Au-delà : nouvelle tentative à +30 min, +2 h, +6 h, +24 h. Après 24 h sans 2xx : envoi en dead letter queue accessible via le dashboard.
Inopay émet des webhooks signés HMAC SHA-256 pour vous notifier des événements clés. Tous les webhooks sont retentés avec backoff exponentiel jusqu'à 24 h, puis envoyés en dead letter queue.
Liste des événements émis par Inopay. Vous choisissez les abonnements lors de l'enregistrement de l'endpoint via POST /v1/webhooks/endpoints.
| Événement | Description |
|---|---|
| order.created | Ordre validé côté Inopay (avant transmission SGI). |
| order.routed | Ordre transmis à la SGI partenaire. |
| order.executed | Ordre exécuté entièrement ou partiellement en bourse. |
| order.failed | Échec d'exécution (rejet SGI, fonds insuffisants, instrument suspendu). |
| kyc.attested | Nouvelle attestation KYC émise et signée. |
| kyc.revoked | Attestation KYC révoquée (ajout à la CRL). |
| audit.snapshot | Snapshot Merkle quotidien publié et ancré. |
| webhook.test | Événement de test déclenché manuellement depuis le dashboard. |
Tous les payloads suivent la même enveloppe : id, type, created_at, data, livemode. Exemple pour order.executed :
POST https://your-app.example.com/webhooks/inopay HTTP/1.1
Content-Type: application/json
X-Inopay-Signature: t=1764082380,v1=4f5a9b2c8e1d3f6a7b9c0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b
X-Inopay-Idempotency-Key: 6c4b0b6e-1c2a-4d09-9b3a-7d1f4e5a6c2d
User-Agent: Inopay-Webhooks/1.0
{
"id": "evt_2A9p3X7q",
"type": "order.executed",
"created_at": "2026-04-25T14:32:14.001Z",
"livemode": true,
"data": {
"order_id": "ord_9Pk2X",
"rcpt_to": "sgi_partner_001",
"instrument": "SNTS.BRVM",
"side": "buy",
"filled_qty": 10,
"average_fill_price_cents": 1248750,
"executed_at": "2026-04-25T14:32:13.880Z",
"exchange_ref": "BRVM-2026-04-25-XK4287"
}
}Header X-Inopay-Signature: t=<timestamp>,v1=<hex>. Concaténez timestamp + '.' + raw_body, calculez le HMAC SHA-256 avec votre secret webhook, puis comparez en temps constant.
// Verify Inopay webhook signature (Node.js >= 20)
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyInopaySignature(
rawBody: string,
header: string,
secret: string,
toleranceSeconds = 300,
): boolean {
// Header format: "t=<unix_ts>,v1=<hex>"
const parts = Object.fromEntries(
header.split(',').map((p) => p.split('=') as [string, string]),
);
const ts = Number(parts.t);
if (!Number.isFinite(ts)) return false;
if (Math.abs(Date.now() / 1000 - ts) > toleranceSeconds) return false;
const expected = createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(parts.v1, 'hex');
return a.length === b.length && timingSafeEqual(a, b);
}# Verify Inopay webhook signature (Python 3.10+)
import hmac, hashlib, time
def verify_inopay_signature(
raw_body: bytes,
header: str,
secret: str,
tolerance_seconds: int = 300,
) -> bool:
# Header format: "t=<unix_ts>,v1=<hex>"
parts = dict(p.split("=", 1) for p in header.split(","))
try:
ts = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - ts) > tolerance_seconds:
return False
signed = f"{parts['t']}.{raw_body.decode()}".encode()
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))// Verify Inopay webhook signature (Go 1.21+)
package webhooks
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strconv"
"strings"
"time"
)
func VerifyInopaySignature(rawBody []byte, header, secret string, tolerance time.Duration) bool {
parts := map[string]string{}
for _, kv := range strings.Split(header, ",") {
if idx := strings.IndexByte(kv, '='); idx > 0 {
parts[kv[:idx]] = kv[idx+1:]
}
}
ts, err := strconv.ParseInt(parts["t"], 10, 64)
if err != nil { return false }
if d := time.Since(time.Unix(ts, 0)); d > tolerance || d < -tolerance {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(parts["t"] + "." + string(rawBody)))
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(parts["v1"]))
}3 retries automatiques : immédiat, +30 s, +5 min. Au-delà : nouvelle tentative à +30 min, +2 h, +6 h, +24 h. Après 24 h sans 2xx : envoi en dead letter queue accessible via le dashboard.
Inopay peut renvoyer le même événement plusieurs fois (réseau, retry). Stockez l'id du payload côté receiver et vérifiez avant de re-traiter. Le header X-Inopay-Idempotency-Key est également présent.
Déclenchez manuellement un événement test depuis le dashboard ou via la CLI :
# Trigger a test event from the dashboard or CLI
inopay-cli webhook test \
--endpoint=ep_4Xk9R \
--event=order.created \
--livemode=false
# Or from the dashboard:
# Settings → Webhooks → endpoint → "Send test event"