UnivaPay Webhook Events — Contract

Source: https://docs.univapay.com/docs/guide/detail/webhook/ (fetched 2026-08-03)

Delivery

  • HTTP POST to the merchant-configured URL (local URLs not allowed)
  • Authorization header contains the merchant-defined auth_token (6+ chars) — the per-supplier
    verification value Yoyacoo stores
  • Receiver MUST return 2xx within 3 seconds (slower = treated as failure, retried)
  • Retries may deliver the same event more than once → idempotent handling required (record the
    top-level id / webhook_id; replay returns 200 without re-processing)
  • Retry stops permanently after 3xx; 4xx/500-502 retry up to 10 times then stop

Verification

  1. Identify supplier by data.store_id (matches user_payment_univapays.store_id)
  2. Compare the incoming Authorization header with the stored auth_token for that supplier
  3. Reject with 401 on mismatch; log and 200 (no side effects) on replay

Events to subscribe to (MVP)

Trigger (console) Event Resource Statuses
課金 (Charge) charge_finished Charge successful / failed / error / canceled (refunded)
返金 (Refund) refund_finished Refund successful / failed
キャンセル完了 (Cancel) cancel_finished Cancel successful / failed / error

Sample payloads

charge_finished — successful

{
  "id": "11ef1288-37b0-2f15-a097-d3a0d4243d4c",
  "event": "charge_finished",
  "data": {
    "id": "11ef1288-399b-eb84-a215-038628c5c460",
    "store_id": "11ecda54-12g0-1c78-bd0f-73aa270d700f",
    "transaction_token_id": "11ef1118-3766-4fdc-baed-b72c5ac3e0ba",
    "transaction_token_type": "one_time",
    "subscription_id": null,
    "requested_amount": 100,
    "requested_currency": "JPY",
    "requested_amount_formatted": 100,
    "charged_amount": 100,
    "charged_currency": "JPY",
    "charged_amount_formatted": 100,
    "fee_amount": null,
    "fee_currency": null,
    "fee_amount_formatted": null,
    "only_direct_currency": true,
    "capture_at": null,
    "descriptor": null,
    "descriptor_phone_number": null,
    "status": "successful",
    "metadata": {},
    "mode": "test",
    "created_on": "2024-05-15T06:56:12.852465Z",
    "redirect": {}
  },
  "created_on": "2024-05-15T06:56:13.140163783Z",
  "webhook_id": "11ee8cf5-2be5-67d8-bc18-7b7c584c5735",
  "successful": true,
  "fired_on": "2024-05-15T06:56:13.092717474Z"
}

charge_finished — failed

{
  "id": "11ef1288-37b0-2f15-a097-d3a0d7843d4c",
  "event": "charge_finished",
  "data": {
    "id": "11ef1288-399b-eb84-a215-038628c5c460",
    "store_id": "11ecda54-12g0-1c78-bd0f-73aa270d700f",
    "transaction_token_id": "11ef1118-3766-4fdc-baed-b72c5ac3e0ba",
    "transaction_token_type": "one_time",
    "subscription_id": null,
    "requested_amount": 100,
    "requested_currency": "JPY",
    "requested_amount_formatted": 100,
    "charged_amount": 100,
    "charged_currency": "JPY",
    "charged_amount_formatted": 100,
    "fee_amount": null,
    "fee_currency": null,
    "fee_amount_formatted": null,
    "only_direct_currency": true,
    "capture_at": null,
    "descriptor": null,
    "descriptor_phone_number": null,
    "status": "failed",
    "error": {
      "code": 309,
      "message": "Test charge failed purposely",
      "details": "Test charge failed purposely"
    },
    "metadata": {},
    "mode": "test",
    "created_on": "2024-05-15T06:56:12.852465Z",
    "redirect": {}
  },
  "created_on": "2024-05-15T06:56:13.140163783Z",
  "webhook_id": "11ee8cf5-2be5-67d8-bc18-7b7c584c5735",
  "successful": true,
  "fired_on": "2024-05-15T06:56:13.092717474Z"
}

refund_finished

{
  "id": "11-0009a6-384c-69d6-accd-eb0c9d8852e7",
  "event": "refund_finished",
  "data": {
    "id": "11-0009a6-384c-69d6-accd-eb0c9d8852e7",
    "store_id": "18886460c-abc6-1cf2-add5-0f8e9e11c64e",
    "charge_id": "118724a6-1d48-4a88-accd-4f1a93609236",
    "status": "successful",
    "amount": 100,
    "currency": "JPY",
    "amount_formatted": 100,
    "reason": null,
    "message": null,
    "error": null,
    "metadata": {},
    "mode": "test",
    "created_on": "2025-09-30T02:36:08.314626Z"
  }
}

cancel_finished

{
  "id": "10033f4a-f683-cc96-b301-77d51deca417",
  "event": "cancel_finished",
  "data": {
    "id": "10033f4a-f683-cc96-b301-77d51deca417",
    "charge_id": "11877f4a-ea7f-1a5e-b301-93a839636a7e",
    "store_id": "11e2200c-a996-1cf2-add5-0f8e9e11c64e",
    "status": "successful",
    "error": null,
    "metadata": {},
    "mode": "test",
    "created_on": "2025-10-02T04:47:56.071145Z"
  }
}

Handling rules

  • charge_finished → look up application by data.metadata (application ID) → update status:
    • successful → payment completed (+ confirmation email, same as Stripe)
    • failed / error → payment failed (customer can retry)
    • canceled → treated as refunded (status mapping)
  • refund_finished / cancel_finished → look up application by data.charge_id
    orders.univapay_charge_id → update status + send notification emails (same as Stripe)
  • Replays: record the top-level id (or webhook_id) — skip processing, return 200
  • Cross-environment guard: validate data.mode matches the environment (test vs live)
  • Unknown store_id or unmatched metadata/charge_id → log as error; never guess another application