Adyen refund issued or failed notifications on your phone.

Get a low-priority push notification when an Adyen refund goes through, and a High-priority one when a refund fails or bounces back.

How it works

Three steps to your first push

STEP 01

Install the recipe

One click in Studio gives you a personal webhook URL.

STEP 02

Paste your URL into Adyen

Add it as a webhook for REFUND, REFUND_FAILED and REFUNDED_REVERSED.

STEP 03

Get a push on your phone

With the text, sound and buttons from the recipe.

Setup

How to set up the Adyen refund, refund_failed and refunded_reversed webhook

  1. Click Install in Studio and sign in. The recipe is added to your account and you get a personal webhook URL.
  2. In your Adyen Customer Area, go to Developers > Webhooks, select Create new webhook and add a Standard webhook.
  3. Paste your webhook URL as the URL and set Method to JSON.https://••••••••/•••••••• your personal URL, shown after install
  4. Under Events settings, check that REFUND, REFUND_FAILED and REFUNDED_REVERSED is selected, then select Save configuration.
  5. Use a **Standard** webhook and set **Method** to **JSON**. The HTTP POST and SOAP methods send the data in a different format that this recipe can't read. `REFUND`, `REFUND_FAILED` and `REFUNDED_REVERSED` are all default events of the Standard webhook.
  6. Successful refunds get a Low-priority push. To only hear about refunds that fail or bounce back, use `your webhook URL?only=failed` as the webhook URL instead.
  7. If you use `/cancelOrRefund`, Adyen reports it as a `CANCEL_OR_REFUND` event, which this recipe doesn't cover.
  8. Adyen's **Test configuration** button can send `REFUNDED_REVERSED` but not `REFUND`, so the most realistic check is to refund a payment in your test Customer Area; that push is marked **(test mode)**. Adyen only needs a 2xx response; the old `[accepted]` response body isn't required.
  9. Adyen signs each event with an HMAC signature in `additionalData.hmacSignature`, but JustPush doesn't check it yet, so keep your endpoint URL private. You can leave the HMAC key and basic authentication settings empty.
The code

What runs when the webhook arrives

Studio calls handleRequest(request) with the incoming webhook and sends the message it returns. It's yours after install; change anything.

transform.js
1// Adyen → REFUND (plus REFUND_FAILED and REFUNDED_REVERSED)
2// Fires when a refund is processed or rejected, when the card scheme later rejects a refund,
3// or when a refund bounces back to your Adyen account.
4// Docs: https://docs.adyen.com/development-resources/webhooks/webhook-types/
5//
6// Needs a Standard webhook (Customer Area > Developers > Webhooks) with Method set to JSON,
7// pointing at the integration's endpoint URL. Add ?only=failed to skip successful refunds.
8
9// Adyen amounts are in minor units, and Adyen sets the decimals per currency. Everything not
10// listed here has two. https://docs.adyen.com/development-resources/currency-codes
11const DECIMALS = {
12 CVE: 0, DJF: 0, GNF: 0, IDR: 0, JPY: 0, KMF: 0, KRW: 0, PYG: 0,
13 RWF: 0, UGX: 0, VND: 0, VUV: 0, XAF: 0, XOF: 0, XPF: 0,
14 BHD: 3, IQD: 3, JOD: 3, KWD: 3, LYD: 3, OMR: 3, TND: 3,
15}
16
17// The event codes this recipe handles.
18const EVENTS = {
19 REFUND: true,
20 REFUND_FAILED: true,
21 REFUNDED_REVERSED: true,
22}
23
24// Own-property lookup, so keys like "toString" fall back instead of hitting Object.prototype.
25function pick(table, key, fallback) {
26 return Object.prototype.hasOwnProperty.call(table, key) ? table[key] : fallback
27}
28
29// A trimmed string, or null. Adyen sometimes sends the literal string "null".
30function text(value) {
31 if (typeof value !== "string") return null
32 const trimmed = value.trim()
33 return trimmed && trimmed.toLowerCase() !== "null" ? trimmed : null
34}
35
36// { value: 1500, currency: "EUR" } → "€15.00". Returns null when either part is missing.
37function money(amount) {
38 const raw = amount?.value
39 const minor = typeof raw === "string" && /^-?\d+$/.test(raw) ? Number(raw) : raw
40 const code = text(amount?.currency)?.toUpperCase()
41
42 if (typeof minor !== "number" || !Number.isFinite(minor) || !code) return null
43
44 const value = minor / 10 ** pick(DECIMALS, code, 2)
45
46 try {
47 return new Intl.NumberFormat("en-GB", { style: "currency", currency: code }).format(value)
48 } catch {
49 return `${value} ${code}`
50 }
51}
52
53// The NotificationRequestItem objects in the body. JSON webhooks carry one, but it's an array.
54function notificationItems(body) {
55 const list = Array.isArray(body?.notificationItems) ? body.notificationItems : []
56 return list.map((wrapper) => wrapper?.NotificationRequestItem).filter((item) => item && typeof item === "object")
57}
58
59// Builds the push for one refund item, or null when it isn't worth one.
60function describe(item, amount) {
61 const reason = text(item.reason)
62 const success = String(item.success).toLowerCase()
63
64 if (item.eventCode === "REFUND" && success === "true") {
65 return {
66 title: "↩️ Refund issued",
67 message: amount ? `${amount} refunded` : "A refund was issued",
68 priority: -1, // Low — you (or a teammate) most likely issued it yourself
69 failed: false,
70 }
71 }
72
73 if (item.eventCode === "REFUND" && success === "false") {
74 return {
75 title: "⚠️ Refund failed",
76 message: `${amount ? `${amount} refund` : "A refund"} couldn't be processed` + (reason ? `: ${reason}` : ""),
77 priority: 1, // High — the shopper is still waiting for their money
78 failed: true,
79 }
80 }
81
82 if (item.eventCode === "REFUND_FAILED") {
83 return {
84 title: "⚠️ Refund failed",
85 message:
86 `${amount ? `${amount} refund` : "A refund"} was rejected by the card scheme` +
87 (reason && reason.toLowerCase() !== "refund failed" ? `: ${reason}` : ""),
88 priority: 1, // High — the shopper is still waiting for their money
89 failed: true,
90 }
91 }
92
93 if (item.eventCode === "REFUNDED_REVERSED") {
94 return {
95 title: "↪️ Refund reversed",
96 message: `${amount ? `${amount} refund` : "A refund"} came back to your Adyen account, so the shopper didn't get it`,
97 priority: 1, // High — the shopper still needs to be paid another way
98 failed: true,
99 }
100 }
101
102 return null
103}
104
105function handleRequest(request) {
106 const body = request.body && typeof request.body === "object" ? request.body : {}
107 const items = notificationItems(body)
108
109 // A custom test notification from Adyen; confirm the connection quietly.
110 const ping = items.find((item) => item.eventCode === "NOTIFICATIONTEST")
111 if (ping) {
112 return {
113 title: "🔔 Adyen connected",
114 message: `Webhook for ${text(ping.merchantAccountCode) ?? "your merchant account"} is working`,
115 topic: "Adyen",
116 priority: -1,
117 }
118 }
119
120 // Guard: only refund items, so other Adyen events on this integration stay quiet.
121 const matches = items.filter((item) => pick(EVENTS, item.eventCode, false))
122 if (!matches.length) {
123 console.log(`Ignoring Adyen "${items[0]?.eventCode ?? "unknown"}" webhook`)
124 return null
125 }
126
127 const item = matches[0]
128 const push = describe(item, money(item.amount))
129 if (!push) {
130 console.log(`Skipping ${item.eventCode} with success "${item.success}"`)
131 return null
132 }
133
134 // ?only=failed on the webhook URL mutes successful refunds.
135 if (String(request.query?.only ?? "").toLowerCase() === "failed" && !push.failed) {
136 console.log("Skipping successful refund (?only=failed)")
137 return null
138 }
139
140 const ref = text(item.merchantReference)
141 const more = matches.length > 1 ? ` (+${matches.length - 1} more)` : ""
142 const test = body.live === "false" || body.live === false ? " (test mode)" : ""
143
144 return {
145 title: push.title,
146 message: push.message + (ref ? ` — ${ref}` : "") + more + test,
147 topic: "Adyen",
148 priority: push.priority,
149 }
150}
Payload

The Adyen REFUND webhook

This is what Adyen sends to your URL for refund, refund_failed and refunded_reversed. It is a sample, trimmed to the fields recipes use.

REFUND · sample.json
1{
2 "live": "true",
3 "notificationItems": [
4 {
5 "NotificationRequestItem": {
6 "amount": {
7 "value": 1500,
8 "currency": "EUR"
9 },
10 "reason": "",
11 "success": "true",
12 "eventCode": "REFUND",
13 "eventDate": "2026-09-29T14:02:11+02:00",
14 "pspReference": "V4HZ4RBFJGXXGN82",
15 "paymentMethod": "visa",
16 "additionalData": {
17 "hmacSignature": "REDACTED_BASE64_SIGNATURE=",
18 "paymentMethodVariant": "visa"
19 },
20 "merchantReference": "ORDER-12345",
21 "originalReference": "QFQTPCQ8HXSKGK82",
22 "merchantAccountCode": "YourCompanyECOM"
23 }
24 }
25 ]
26}
FAQ

Adyen refund issued or failed notifications: questions

Does this work on iPhone and Android?

Yes. Install the JustPush app from the App Store or Google Play and sign in. Every phone signed in to your account gets the notification.

Do I need to write code?

No. Install the recipe in Studio, paste your webhook URL into Adyen and you are done. The code is there if you want to change the text, the sound or the buttons.

Can I change what the notification says?

Yes. After install the recipe's code is yours. Edit it in Studio and test it against the sample payload before you save.

What does it cost?

JustPush is free for 30 days. After that it's $19.99 a year, or $39.99 once. There is no extra charge for recipes.

Ready when you are

Adyen on your phone in two minutes.

Install the recipe, paste one URL, done. Free for 30 days, no credit card required.