Z Zise Developers 简体中文

card.3ds.received

Card 3DS / OTP verification code received

When Emitted

Emitted after our issuing provider sends a 3DS / OTP verification code callback and it is successfully stored in card_3ds_events. Currently only two integrations produce this event: WAS and INT. data.id is the public card resource ID itself (crd_<id>); retrieval still uses GET /v1/cards/{id}. The actual verification code is exposed through three controlled additional fields in the event body: - otp_code: the short code for the user to enter manually. - expires_at: the code’s expiration time, populated only when supplied by the upstream. - application_id: for a card originating from an application, the public cap_<id> identifies that application so you can associate it directly; cards without an originating application omit this field. ⚠ The event body does not expose our internal event_key or identify the upstream provider. Merchants should associate records using only the member, card, and application identifiers they already hold. ⚠ This is different from card.status.updated: receiving a code does not mean the card status changed. In particular, WAS continues attempting automatic activation after an activation code arrives. Check the subsequent card.activated event or retrieve card status to determine success; do not treat this event as successful activation.

This event is not initiated by an API call

Triggered after a 3DS or OTP callback from our issuing provider is stored; not initiated directly by a merchant OpenAPI endpoint.

Payload

{
  "event_id": "evt_8dc2a13d65904ef29c5ea2f2fe7a1dc2",
  "event_type": "card.3ds.received",
  "created_at": "2026-09-07T11:38:19Z",
  "merchant_id": "acme",
  "livemode": true,
  "data": {
    "object": "card",
    "id": "crd_e70b3d41-5a28-4c96-91f0-6b2a8c05d7e3",
    "external_member_id": "u_88123",
    "status": "received",
    "status_version": 13,
    "application_id": "cap_3882c371-572f-4d5a-af08-f61fc0964de9",
    "otp_code": "54695634",
    "expires_at": "2026-09-07T11:38:19.549Z"
  }
}

Payload Fields

FieldTypeDescription
idstringUse this for deduplication. evt_… remains unchanged when the same event is redelivered.
typestringAlways card.3ds.received
created_atstringTime the event was created (RFC3339), not its delivery time. It is unchanged on redelivery.
data.objectstringObject type; determines which endpoint to query with data.id
data.idstringObject ID; use it to retrieve details.
data.statusstringTreat unrecognized values as unknown and raise an alert; do not fall back to “processing”
data.status_versionnumberMonotonically increasing; use it to discard older states that arrive late.

Signature Verification and Deduplication

Verify the signature against the raw request body bytes. Do not parse and reserialize the JSON: your JSON library may change key order or whitespace, which changes the signature and can look like a key configuration error.

// Node · Run before parsing JSON
const raw = await readRawBody(req);            // Buffer / string; do not use parsed req.body
const expect = crypto.createHmac("sha256", WEBHOOK_SECRET).update(raw).digest("hex");
const got = req.headers["z-signature"];        // Format: t=<unix>,v1=<hex>
if (!timingSafeEqual(expect, parseV1(got))) return res.status(400).end();

// Deduplicate using the envelope id, not data.id
if (await seen(JSON.parse(raw).id)) return res.status(200).end();

For the full procedure, including timestamp tolerance and redelivery semantics, see Webhook Guide.