# Alipay Webhooks

Alipay's global / cross-border products — Antom (Cashier Payment / AMS) and
Alipay+ — deliver asynchronous webhook notifications (`notifyPayment`,
`notifyRefund`, `notifyCapture`, `notifyAuthorization`, `notifyDispute`) signed
with an asymmetric RSA256 (SHA256withRSA) scheme carried in a `Signature`
header. This skill targets that header-based scheme.

> Legacy note: The older Alipay openapi / MAPI integration
> (`openapi.alipay.com`, `global.alipay.com`) is a different, unrelated
> scheme — form-encoded params with `sign` + `sign_type=RSA2`, verified by
> stripping `sign`/`sign_type`, sorting the remaining params A–Z, joining with
> `&`, and replying with the plain text `success`. If your integration posts
> `application/x-www-form-urlencoded` bodies with a `sign` field, you are on
> that older vintage — this skill does not cover it. Everything below is the
> modern Antom/Alipay+ header RSA256 scheme.

## When to Use This Skill

* How do I receive Alipay / Antom / Alipay+ webhooks?
* How do I verify the Alipay `Signature` header (RSA256 / SHA256withRSA)?
* How do I handle `notifyPayment`, `notifyRefund`, or `notifyDispute` events?
* Why is my Alipay webhook signature verification failing (base64URL encoding)?
* How do I sign the acknowledgement response Antom expects?

## Verification (core)

Alipay/Antom signs each request with SHA256withRSA using its private key and
carries the result in a `Signature` header. You verify it with Antom's public
key (from the Dashboard). Three details trip people up:

1. The signed content is exactly two lines: `<METHOD> <URI>` then
  `<Client-Id>.<Request-Time>.<RawBody>` joined by single periods.
2. The signature is base64URL encoded (URL-safe alphabet), and is often
  additionally percent-encoded on the wire — URL-decode, then base64-decode.
3. Use the raw request body — never re-serialize parsed JSON first.

```javascript
const { createVerify } = require('crypto');

// Header: "algorithm=RSA256,keyVersion=1,signature=<urlEncoded base64url sig>"
function parseSignatureHeader(header) {
  return Object.fromEntries(
    header.split(',').map((p) => {
      const i = p.indexOf('=');
      return [p.slice(0, i).trim(), p.slice(i + 1).trim()];
    })
  );
}

function verifyAlipay({ method, uri, clientId, requestTime, rawBody, signatureHeader, publicKey }) {
  const { signature } = parseSignatureHeader(signatureHeader);
  if (!signature) return false;
  const content = `${method} ${uri}\n${clientId}.${requestTime}.${rawBody}`;
  // Percent-decode, normalize URL-safe base64 → standard, then decode.
  const sig = Buffer.from(decodeURIComponent(signature).replace(/-/g, '+').replace(/_/g, '/'), 'base64');
  const v = createVerify('RSA-SHA256');
  v.update(content, 'utf8');
  v.end();
  try {
    return v.verify(publicKey, sig); // publicKey = Antom/Alipay+ PEM public key
  } catch {
    return false;
  }
}

```

Signing the acknowledgement — unlike most providers, Antom expects the ack
itself to be signed with your private key over the same two-line content
(`<METHOD> <URI>\n<Client-Id>.<Response-Time>.<ResponseBody>`), returned in a
`Signature` header alongside `Client-Id` and `Response-Time`. See the examples.

> For complete handlers (header parsing, response signing, event dispatch, tests), see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/alipay-webhooks/examples/express/)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/alipay-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/alipay-webhooks/examples/fastapi/)

## The Acknowledgement Response

Respond HTTP 200 with this exact body so Antom stops retrying:

```json
{ "result": { "resultCode": "SUCCESS", "resultStatus": "S", "resultMessage": "Success" } }

```

Include these response headers (the ack is signed):

* `Client-Id` — your Client ID
* `Response-Time` — ISO 8601 timestamp (e.g. `2026-07-24T10:00:00Z`)
* `Signature` — `algorithm=RSA256,keyVersion=1,signature=<your base64url sig>`

If the ack is missing or non-200, Antom retries ~8 times over 24 hours
(0s, 2m, 10m, 10m, 1h, 2h, 6h, 15h). Make your handler idempotent.

## Common Event Types

Antom notifications are distinguished by the `notifyType` field in the body
(there is no `type` field), plus `result.resultStatus` (`S` success, `F` fail,
`U` unknown/pending).

| `notifyType` | Notification method | Fires when |
| --- | --- | --- |
| `PAYMENT_RESULT` | `notifyPayment` | A payment reaches a final success/failure state |
| `CAPTURE_RESULT` | `notifyCapture` | A capture succeeds or fails (auth/capture flow) |
| `REFUND_RESULT` | `notifyRefund` | A refund finishes processing |
| `AUTHORIZATION_RESULT` | `notifyAuthorization` | An authorization is granted or cancelled |
| `DISPUTE_CREATED` / `DISPUTE_JUDGED` | `notifyDispute` | A dispute is opened or judged |

> For the full notification reference, see [Antom notifications](https://docs.antom.com/ac/cashierpay/notifications).

## Environment Variables

```bash
ALIPAY_CLIENT_ID=SANDBOX_5YC47N2ZQHJ004124        # Your Client ID (from the Dashboard)
ALIPAY_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"          # Antom/Alipay+ public key — verifies inbound
ALIPAY_MERCHANT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"  # Your private key — signs the ack

```

The notify URL is set per API call via `paymentNotifyUrl` / `refundNotifyUrl`
in `pay()` / `createPaymentSession()` / `refund()` (a Dashboard URL is the
fallback). There is no single shared "webhook secret" — verification is
asymmetric key-based.

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 alipay --path /webhooks/alipay

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/alipay-webhooks/references/overview.md) - Alipay/Antom webhook concepts and events
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/alipay-webhooks/references/setup.md) - Dashboard configuration and key management
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/alipay-webhooks/references/verification.md) - RSA256 signature verification and response signing