Skip to content

Webhook (Async Callback) Guide

SoPay sends an HTTP POST to your notifyUrl (or default callbackUrl) when a payin or payout order reaches status=completed.

Verify signature, do not decrypt. The JSON body is plaintext. The platform signs with its private key; you verify X-Sign with the platform public key.

Keys

DirectionSigns withVerify with
Merchant → Platform (API calls)Merchant private keyPlatform uses merchant public key
Platform → Merchant (webhooks)Platform private keyMerchant uses platform public key

Obtain the platform public key from the merchant portal (Profile → Security → Platform RSA public key, or GET /portal/profileplatformPublicKey).

When webhooks fire

OrderTriggerURL
Payinstatus=completednotifyUrl on create; no webhook if empty
Payoutstatus=completednotifyUrl or merchant default callbackUrl

Duplicate deliveries are possible — handle idempotently by merchantOrderNo / orderNo.

HTTP format

  • POST, Content-Type: application/json
  • Headers: X-Merchant-No, X-Timestamp (Unix seconds), X-Nonce, X-Sign (Base64 RSA2)

Body fields

FieldTypeNotes
orderTypestringpayin or payout
orderNostringPlatform order id
merchantOrderNostringYour order id
statusstringcompleted on success webhooks
amountstringOrder amount
feeAmountstringFee
netAmountstringPayin: credit amount; Payout: total debit incl. fee
completedAtnumberUnix time in seconds

Time fields (API & webhooks)

All merchant-facing time values use Unix timestamps in seconds (JSON number), including:

  • Open API responses: createdAt, completedAt
  • Webhook body: completedAt
  • Webhook headers: X-Timestamp

Admin / portal list APIs for merchant orders, wallet transactions, and callbacks use the same convention.

Signature verification

  1. Flatten top-level JSON body to map[string]string
  2. Add timestamp and nonce from headers
  3. Sort keys ASCII ascending, join k=v&...
  4. RSA2-verify X-Sign with platform public key

Body flattening

JSON typeSigned value
stringAs-is (empty omitted)
number (e.g. completedAt)Decimal integer string, e.g. 1782121882 (no scientific notation)
object / arrayJSON string

Worked example (with completedAt)

Sign string:

amount=2000&completedAt=1782121882&feeAmount=6&merchantOrderNo=h7z3jskx7h7j8j&netAmount=1994&nonce=e63b411b-f379-4e69-9de8-d58c192d3e90&orderNo=PI202606220949069037e0&orderType=payin&status=completed&timestamp=1782121883

Use portal API Debug → Callback verify to compare sign strings locally.

ACK & retries

Respond HTTP 200 with body exactly OK after verify + successful business update. Otherwise the platform retries (up to ~200 attempts). See Chinese guide for full Go/JS/Python samples.

Released under the MIT License.

2-1-2 Nihonbashi-Hongokucho,Chuo-ku,Tokyo