Skip to main content

Documentation Index

Fetch the complete documentation index at: https://partner-docs.contro.dev/llms.txt

Use this file to discover all available pages before exploring further.

Event: card.3ds_otp

Sent when the card network requests a 3D Secure (3DS) one-time passcode (OTP) for a card issued under your partner program. Your application is responsible for delivering the OTP to the cardholder via your preferred channel (SMS, email, push notification, in-app).
3DS OTPs are time-sensitive. Deliver the OTP to your cardholder within 60 seconds of receiving this event. After that window the cardholder’s transaction may fail.

Payload

FieldTypeDescription
cardIdstringContro card ID the OTP applies to
cardholderIdstring | nullPartner cardholder ID associated with the card
last4stringLast 4 digits of the card number, for display in your message to the cardholder
otpCodestringThe 3DS one-time passcode to deliver to the cardholder
transactionAmountstring | undefinedTransaction amount the OTP authorizes, in major units (e.g. "42.50")
transactionCurrencystring | undefinedISO 4217 currency code of the transaction
merchantstring | undefinedMerchant name attempting the transaction
timestampstringISO 8601 timestamp the event was generated
{
  "cardId": "card_xyz789",
  "cardholderId": "ch_abc123",
  "last4": "0000",
  "otpCode": "123456",
  "transactionAmount": "42.50",
  "transactionCurrency": "USD",
  "merchant": "Coffee Shop",
  "timestamp": "2026-04-16T10:00:00Z"
}

Response

Your endpoint must return a 2xx status code within 30 seconds to acknowledge receipt. Any non-2xx response or timeout triggers the retry policy, but note that retries past the 60-second OTP validity window will not help the cardholder complete the transaction.
Status codeMeaning
200OTP received; you have dispatched it to the cardholder
202Received; will dispatch asynchronously
Any non-2xxDelivery failed — Contro will retry per the retry policy

Example handler

app.post("/webhooks/contro", async (req, res) => {
  const eventType = req.headers["x-contro-event"];

  if (eventType === "card.3ds_otp") {
    const { cardholderId, last4, otpCode, merchant, transactionAmount, transactionCurrency } = req.body;

    const cardholder = await db.cardholders.findById(cardholderId);
    await sms.send(cardholder.phoneNumber, {
      message: `Your verification code for the ${transactionCurrency} ${transactionAmount} purchase at ${merchant} (card ending ${last4}) is ${otpCode}.`,
    });
  }

  res.status(200).send("OK");
});

Security considerations

  • Treat otpCode as sensitive: do not log it, do not persist it past the immediate dispatch.
  • Verify the X-Contro-Signature header before trusting the payload — see signature verification.
  • Use a fast, idempotent dispatch path. Retries can deliver the same otpCode more than once; sending it twice to the cardholder is acceptable, but failing to send it on the first attempt is not.