V-REC Webhook lifecycle & delivery timing
Understand exactly when registration, KYC, tenant-funding and return events are queued, which statuses they carry, and how MatchMove 3-D Secure works separately.
1. Who sends what?
V-REC → Merchant:signed asynchronous business events to the Merchant's configured HTTPS webhook URL.
SAKURA → V-REC:signed KYC change signals; V-REC re-fetches the authoritative result.
V-REC → Partner:payout lifecycle on the separate Partner webhook channel.
MatchMove → cardholder/checkout:issuer 3-D Secure challenge, separate from the V-REC Merchant webhook.
2. Event names & reported statuses
| Merchant event | Trigger | Statuses / state |
|---|---|---|
user.registered | Merchant API/Portal, public registration, or consent link completes | membership_status(typically ACTIVE)、access_mode(FULL / READONLY)、kyc_status snapshot |
user.kyc.updated | SAKURA signal processed; latest state re-read | NOT_STARTED, PENDING, REVIEW, CORRECTION_REQUIRED, APPROVED, REJECTED |
tenant.funding.allocation.updated | Direct allocation result persisted | COMPLETED, REJECTED, FAILED, UNKNOWN, RECONCILE_REQUIRED, MISMATCH |
tenant.funding.return.updated | Return/reversal state resolved | COMPLETED, FAILED, RECONCILE_REQUIRED |
webhook.test | Merchant Portal test action | Connectivity only |
Events carry id, type, occurred_at and event-specific data are included. Treat status fields as case-sensitive program values, not translated labels. No V-REC Merchant 3DS event is implemented.
3. Member registration — API & Webhook
GET /api/merchant/v1/profile-options
POST /api/merchant/v1/users + HMAC + Idempotency-Key
→ SAKURA registers/looks up authoritative identity
→ V-REC membership and login record committed
→ optional V-REC / SAKURA onboarding emails
→ user.registered queued for configured Merchant
← HTTP 201 { user, notifications, activation_url if applicable }
Next cron worker tick → POST Merchant HTTPS callback
Merchant → GET /api/merchant/v1/users/{id} for verification
HTTP 201 is immediate; user.registered is asynchronous and deduplicated per Merchant/user. Public registration has no Merchant POST/201. Existing-user consent-link registration emits the event on confirmed consent, not on link creation. Registration never implies KYC approval. Password setup links are never included in webhooks.
4. Direct Tenant Funding — API & Webhook
GET /api/merchant/v1/tenant-funding/balance
GET /api/merchant/v1/tenant-funding/users/{id}/capacity
POST /api/merchant/v1/tenant-funding/allocations
+ Idempotency-Key + tenant_funding.write
→ ACTIVE / FULL / KYC APPROVED + same source/target Merchant
→ SAKURA tenant management confirmation and /fund call
→ COMPLETED: V-REC ledger lot created with expiry snapshot
→ tenant.funding.allocation.updated queued
← HTTP 201 COMPLETED
← HTTP 409 REJECTED (e.g. insufficient balance; no funds moved; final)
← HTTP 202 UNKNOWN etc. (retry with the same Idempotency-Key)
GET /api/merchant/v1/tenant-funding/allocations?updated_since=… (cron list)
GET /api/merchant/v1/tenant-funding/allocations/{id}
POST /api/merchant/v1/tenant-funding/allocations/{id}/reverse
POST /api/merchant/v1/tenant-funding/users/{id}/return
→ tenant.funding.return.updated
Only COMPLETED confirms that a direct allocation is credited. UNKNOWN, RECONCILE_REQUIRED, MISMATCH must never be treated as available funds. REJECTED is final and means no funds moved; resending the same Idempotency-Key returns the same result (fix the cause and use a new key). Without webhooks, poll GET /api/merchant/v1/tenant-funding/allocations?updated_since=…. Reuse the same Idempotency-Key for ambiguous results and never repeat an unknown transfer blindly. SAKURA is authoritative for Tenant balances and original lot expiry (including a possible 180-day policy). READONLY membership cannot perform funding or MatchMove user-to-user transfers.
Partner-side payout.credited belongs to a different notification channel; do not substitute it for direct allocation results.
5. KYC state updates
MatchMove / SAKURA KYC state change
→ SAKURA signed notification → V-REC
→ V-REC reads current SAKURA status
→ V-REC queues user.kyc.updated
→ Merchant GET /api/merchant/v1/users/{id}/kyc
Treat the webhook as an alert and use GET for current state. Duplicate or delayed provider notifications must not overwrite newer KYC data.
6. When does V-REC send?
Events enter the delivery queue when a source operation commits. The worker gateway/bin/deliver-merchant-webhooks.php must run once per minute. Initial delivery occurs at the next worker tick, not at a promised second. An HTTPS callback URL and secret must be configured before the event. Older events are not automatically backfilled.
Delivery states: PENDING → SENDING → DELIVERED(HTTP 2xx)、otherwise RETRY、after eight attempts FAILED is final. Retry delays are 60 seconds, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours, plus worker tick latency. Inspect Merchant Portal → Merchant API → Webhook delivery history. Receivers must deduplicate event IDs and expect at-least-once delivery.
7. Signed request verification
X-VREC-Event: user.registered
X-VREC-Delivery: mwd_...
X-VREC-Timestamp: 1791532800
X-VREC-Signature: lowercase hex HMAC-SHA256
body_hash = hex(SHA256(raw_body))
message = timestamp + "\n" + delivery + "\n" + event + "\n" + body_hash
signature = hex(HMAC-SHA256(message, MerchantWebhookSecret))
Compute against the unmodified raw body; use constant-time comparison, replay window and idempotent processing. The Webhook Secret is distinct from the Merchant API credential secret. Partner webhook signatures use a different contract without the delivery ID.
7b. Partner webhooks (Corporate Payout)
V-REC notifies the HTTPS webhook URL registered in the Partner settings by a V-REC administrator when a payout is created and when it is credited. The signature contract differs from Merchant webhooks and carries no delivery ID or event ID.
X-VREC-Event: payout.created | payout.credited
X-VREC-Timestamp: 1792540800
X-VREC-Signature: lowercase hex HMAC-SHA256
body_hash = hex(SHA256(raw_body))
message = timestamp + "\n" + event + "\n" + body_hash
signature = hex(HMAC-SHA256(message, PartnerWebhookSecret))
{
"event_version": "1.0",
"payout": {
"id": "po_...", "beneficiary_id": "VREC-...",
"amount": "100.00000000", "currency": "USD",
"external_reference": "INVOICE-2026-10-001", "purpose": "corporate_payout",
"funding_mode": "MERCHANT_BALANCE",
"source_asset": "MATCHMOVEUSD", "destination_asset": "MATCHMOVEUSD",
"status": "PENDING_FUNDS", "underlying_fund_confirmed": false,
"created_at": "2026-10-21 01:00:00", "credited_at": null
}
}
- Only payout.created and payout.credited are sent. Cancellation, RETRYABLE, RECONCILE_REQUIRED and approval waits are not notified; poll GET /api/v1/payouts/{id}.
- Each event occurs once per payout. Retries resend the same body (only timestamp and signature change), so deduplicate on X-VREC-Event + payout.id.
- For historical LEGACY_REFERENCE payouts credited through Tenant Funding, payout.credited carries a short payout object with id / status / amount / currency only.
- Non-2xx responses are retried after 60 s, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h, up to 8 attempts. Compare the timestamp with your clock and reject stale notifications.
8. MatchMove 3-D Secure: provider-driven authentication
Cardholder → online checkout / acquirer
→ card network → MatchMove issuer / ACS
→ provider challenge (OTP, app or browser) to cardholder
→ cardholder completes challenge
→ ACS result to checkout / acquirer
→ card authorization via card scheme/issuer
3-D Secure notifications and challenges are sent or controlled by MatchMove or its issuer authentication provider, subject to the card program. V-REC does not send 3DS OTPs and does not expose a 3ds.* Merchant webhook in this version. Issuer-specific callback endpoints and status mappings require a program contract and verified integration before they can be called implemented.