OuiPay
Guides

Handle Webhooks

Receive, verify, and process OuiPay events safely.

Webhook receiving flow

sequenceDiagram
    autonumber
    participant OuiPay as OuiPay Event Engine
    participant App as Merchant Server
    participant Cache as Deduplication Store
    participant Queue as Async Job Queue

    OuiPay->>App: POST /webhooks/ouipay (X-OuiPay-Signature, X-OuiPay-Timestamp)
    App->>App: 1. Verify HMAC SHA256 Signature
    alt Invalid Signature or Timestamp > 5m
        App-->>OuiPay: 401 Unauthorized
    else Valid Request
        App->>Cache: 2. Check event_key exists
        alt Duplicate Event Key
            Cache-->>App: Already processed
            App-->>OuiPay: 200 OK (ignored duplicate)
        else New Event Key
            App->>Cache: Store event_key
            App->>Queue: 3. Dispatch to worker queue
            App-->>OuiPay: 200 OK (Acknowledged)
        end
    end

1. Create a subscription

In the dashboard (or POST /v1/webhooks/subscriptions if your integration is self-serve), register your endpoint and event types. Save the returned signing secret: shown once.

2. Verify the signature

Reject anything that fails verification before any processing:

import crypto from "crypto";

function verify(
  rawBody: string,
  signature: string,
  timestamp: string,
  secret: string,
) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(`sha256=${expected}`),
    Buffer.from(signature),
  );
}

Also reject X-OuiPay-Timestamp older than 5 minutes: replay protection.

3. Dedupe on event_key

Deliveries are at-least-once. Store processed event_keys and skip repeats before any side-effect: a payment.completed replay must not fulfill twice.

4. Respond fast, process async

Return 200 quickly; do the work in a queue. A slow endpoint reads as a failure and triggers retries.

app.post("/webhooks/ouipay", async (req, res) => {
  if (
    !verify(
      req.rawBody,
      req.headers["x-ouipay-signature"],
      req.headers["x-ouipay-timestamp"],
      SECRET,
    )
  ) {
    return res.status(401).end();
  }
  const { event_key, event_type, data } = req.body;
  if (await alreadyProcessed(event_key)) return res.status(200).end();
  await queue.push({ event_key, event_type, data });
  res.status(200).end();
});

5. Map events to your state

  • payment.completed → mark the order paid / credit the service
  • transfer.completed → mark the payout done
  • *.failed → read failure.code/failure.stage, surface the reason, release any reserved resource
  • wallet.funded → credit the customer's balance in your system

Common pitfalls

  • Verifying the parsed body: HMAC covers the raw bytes; verify before JSON parsing
  • Trusting event order: use occurred_at and transaction status, not arrival order
  • Blocking on slow work: ack fast, process async
  • No dedupe: replays are normal; event_key is the guard

On this page