# Adyen Webhooks

## When to Use This Skill

* How do I receive Adyen webhooks (standard notifications)?
* How do I verify Adyen webhook HMAC signatures?
* How do I handle AUTHORISATION, CAPTURE, REFUND, or CHARGEBACK events?
* Why is my Adyen HMAC signature verification failing?
* What response body does Adyen expect from my webhook endpoint?

## How Adyen Webhooks Work

Adyen sends standard notifications as an HTTP POST with a JSON body containing
a batch of `notificationItems`. Each item wraps a `NotificationRequestItem`:

```json
{
  "live": "false",
  "notificationItems": [
    {
      "NotificationRequestItem": {
        "eventCode": "AUTHORISATION",
        "success": "true",
        "pspReference": "7914073381342284",
        "merchantAccountCode": "TestMerchant",
        "merchantReference": "TestPayment-1407325143704",
        "amount": { "value": 1130, "currency": "EUR" },
        "additionalData": { "hmacSignature": "coqCmt/IZ4E3CzPvMY8zTjQVL5hYJUiBRg8UU+iCWo0=" }
      }
    }
  ]
}

```

Your endpoint must:

1. Verify the HMAC signature on each item (`additionalData.hmacSignature`).
2. Acknowledge with the literal body `[accepted]` and HTTP 200 — otherwise Adyen retries.

## Verification (core)

Adyen's HMAC is not computed over the raw request body. It is computed over a
`:`-delimited string of specific fields, in this exact order:

```
pspReference : originalReference : merchantAccountCode : merchantReference : amount.value : amount.currency : eventCode : success

```

Each field value is escaped (`\` → `\\`, `:` → `\:`), empty fields become empty
strings, and the HMAC key from the Customer Area is a hex string that must be
hex-decoded before use. The result is HMAC-SHA256, base64-encoded, and compared
against `additionalData.hmacSignature`. Because the signature covers parsed fields,
you parse the JSON first, then verify each item.

The official `@adyen/api-library` SDK does all of this for you:

```javascript
const { hmacValidator } = require('@adyen/api-library');

const validator = new hmacValidator();

// item = notificationItems[i].NotificationRequestItem (plain parsed object)
// ADYEN_HMAC_KEY = hex string from the Customer Area
const valid = validator.validateHMAC(item, process.env.ADYEN_HMAC_KEY);
// reads item.additionalData.hmacSignature and compares (timing-safe) internally

```

No Node SDK (e.g. Python/FastAPI)? Reproduce the algorithm manually:

```python
import hmac, hashlib, base64, binascii

def calculate_hmac(item, hex_key):
    a = item.get("amount") or {}
    fields = [item.get("pspReference", ""), item.get("originalReference", ""),
              item.get("merchantAccountCode", ""), item.get("merchantReference", ""),
              a.get("value", ""), a.get("currency", ""),
              item.get("eventCode", ""), item.get("success", "")]
    data = ":".join(str(f).replace("\\", "\\\\").replace(":", "\\:") for f in fields)
    key = binascii.unhexlify(hex_key)  # hex → bytes
    return base64.b64encode(hmac.new(key, data.encode("utf-8"), hashlib.sha256).digest()).decode()

# hmac.compare_digest(calculate_hmac(item, key), item["additionalData"]["hmacSignature"])

```

> For complete handlers with route wiring, event dispatch, Basic Auth, and tests, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/adyen-webhooks/examples/express/)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/adyen-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/adyen-webhooks/examples/fastapi/)

## Common Event Types

| `eventCode` | Triggered When |
| --- | --- |
| `AUTHORISATION` | A payment was authorised (check `success` for the outcome) |
| `CAPTURE` | Authorised funds were captured |
| `CAPTURE_FAILED` | A capture attempt failed |
| `REFUND` | A refund was processed |
| `REFUND_FAILED` | A refund attempt failed |
| `CANCELLATION` | An authorisation was cancelled |
| `CANCEL_OR_REFUND` | A payment was cancelled or refunded |
| `CHARGEBACK` | Funds were reversed by the shopper's bank |
| `NOTIFICATION_OF_CHARGEBACK` | A chargeback dispute was opened |
| `REPORT_AVAILABLE` | A generated report is ready to download |

> Important: Always check the `success` field — an `AUTHORISATION` with
> `success: "false"` means the payment was refused, not approved.

> For the full event reference, see [Adyen webhook types](https://docs.adyen.com/development-resources/webhooks/webhook-types).

## Environment Variables

```bash
ADYEN_HMAC_KEY=YOUR_HEX_HMAC_KEY        # Hex string generated in the Customer Area
# Optional Basic Auth (recommended) — configured alongside the webhook in the Customer Area
ADYEN_WEBHOOK_USERNAME=your_username
ADYEN_WEBHOOK_PASSWORD=your_password

```

## Local Development

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

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/adyen-webhooks/references/overview.md) - Adyen webhook concepts and event types
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/adyen-webhooks/references/setup.md) - Configure webhooks in the Customer Area, generate the HMAC key
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/adyen-webhooks/references/verification.md) - HMAC signature verification details and gotchas