Webhook Notifications

Our webhooks inform you of asynchronous payment status updates and events that are not directly triggered by request from your side.

To process notifications, you need to:

  1. Expose an endpoint on your server.

  2. Activate webhook notifications.

  3. Understand the types of notifications.

Expose an endpoint on your server

To receive notifications you need a server with an endpoint that can receive a JSON HTTP POST request.

The request body will contain an array of events. Each event object will have an event attribute which will decide the format of the additionalData attribute.

{
    "events": [
        {
            "event": "someEvent",
            "id": "1",
            "triggeredAt": "1970-01-01T00:00:00Z",
            "additionalData": {}
        }
    ]
}

A request header, X-HMAC-SHA256-Signature, will also be present in the request. This should be used to verify that the request was sent by Kronor and that it hasn’t been tampered with. This signature is the result of applying the HMAC Function to the unparsed request body together with the hmacSecret returned by Kronor when you configure your webhook URL. (See Activate webhook notifications.)

Your server should acknowledge the notification by responding with HTTP 200 and [accepted] in the response body. The notification will be considered unsuccessful if the server takes longer than 10 seconds to reply.

We recommend that you store the notifications in their raw format, acknowledge the request and then process the notifications. That way you don’t lose any data in case of an error, and you won’t lose data in the webhooks that you aren’t specifically listening for, allowing you to process them at a later point in time.

Here’s an example Node JS server that accepts webhook requests and verifies the HMAC header.

const crypto = require('crypto')
// npm i express
const express = require('express')
// npm i body-parser
const bodyParser = require('body-parser')

const app = express()
const port = 12345
const hmacSecret = '77fd4252-cba8-4612-aa06-f3dad513e3b9' // TODO: add your own secret here

// middleware necessary to be able to get the raw body from express
const rawBodySaver = (req, res, buf, encoding) => {
  if (buf && buf.length) {
    req.rawBody = buf.toString(encoding || 'utf8')
  }
}

const options = {
  verify: rawBodySaver
}

app.use(bodyParser.json(options))

app.post('/webhook', (req, res) => {
  const hmacSignature = req.headers['x-hmac-sha256-signature']
  const rawBody = req.rawBody

  const computedSignature = crypto
                              .createHmac('SHA256', hmacSecret)
                              .update(rawBody)
                              .digest('hex')

  if (hmacSignature !== computedSignature) {
    res.status(401)
    res.send('unexpected hmac signature')
    return
  }

  // store the events in your database

  console.log(req.body)
  res.send('[accepted]')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

Activate webhook notifications

Loading GraphiQL...

Note that subsequent mutations will not generate a new hmacSecret, the original secret will be returned again.

Understand the types of notifications

Each notification contains an event attribute that specifies which type of event triggered the notification.

Note that all money is denoted in minor units. I.e.

{
    "amount": 1234,
    "currency": "EUR"
}

means 12 Euro and 34 Cents.

invoiceUpdate

Triggers at various points during the invoice flow of a customer.

{
    "events": [
        {
            "event": "invoiceUpdate",
            "id": "1",
            "triggeredAt": "1970-01-01T00:00:00Z",
            "additionalData": {
                "invoiceInfo": {
                    "purchaseId": "2",
                    "customerName": "Alan Turing",
                    "expectedAmount": 10000,
                    "expectedAmountFormatted": "100,00 kr",
                    "dueDate": "1970-01-01T00:00:00Z",
                    "dueDateFormatted": "1 Januari 1970",
                    "orderNumber": "b11511e0-048a-481f-b61d-9b8d6a91cbb6",
                    "invoicePageJwt": "eyJzZWNyZXRfbWVzc2FnZSI6ICJ3aHkgYXJlIHlvdSByZWFkaW5nIHRoaXMgeW91IG5lcmQ/In0=",
                    "reminderFeeAmount": "3000",
                    "reminderFeeAmountFormatted": "0,00 kr",
                    "currency": "SEK"
                },
                "invoiceEvent": "<INVOICE_EVENT>"

            }
        }
    ]
}

Where the possible values of <INVOICE_EVENT> are:

  • invoiceCreated

  • invoiceSettled

  • invoiceDebtCollection

  • invoiceTransferringToAccount

  • invoiceReminder

  • invoiceSoftreminder

paymentStateUpdate

Triggers at various points during the payment flow of a customer.

{
    "events": [
        {
            "event": "paymentStateUpdate",
            "id": "1",
            "triggeredAt": "1970-01-01T00:00:00Z",
            "additionalData": {
                "state": "<PAYMENT_STATE>",
                "paymentType": "<PAYMENT_TYPE>",
                "paymentInfo": {
                    "amount": 10000,
                    "amountPaid": 5000,
                    "amountAuthorized": 15000,
                    "amountToBePaid": 5000,
                    "currency": "SEK",
                    "reference": "b11511e0-048a-481f-b61d-9b8d6a91cbb6",
                    "reference2": "this is merchant reference 2"
                }
            }
        }
    ]
}

Where the possible values of <PAYMENT_STATE> are:

  • FLOW_COMPLETED

  • ACCEPTED

  • AUTHORIZED

  • PAID

  • CANCELLED

  • CAPTURE_DECLINED

where CAPTURE_DECLINED is:

for payments that cannot be captured anymore as the card is declined from the payment gateway. The actionable step here, for you as the merchant, is to contact the customer to pay the amountToBePaid via other means.

where FLOW_COMPLETED is:

for bank transfer payments, the user interaction has been completed and we are waiting for confirmation from the bank servers. The actionable step here, for the merchant, is to let the customer know the order has been accepted and that it’ll be confirmed once the funds are received.

where ACCEPTED is:

for bank transfer payments, the funds have been debited from the customers bank account. The merchant should wait for PAID state before shipping the order.

And the possible values of <PAYMENT_TYPE> are:

  • SWISH

  • BANK_TRANSFER

  • CREDIT_CARD

  • MOBILEPAY

  • VIPPS

captureStateUpdate

Triggers after a successful or failed payment capture.

{
    "events": [
        {
            "event": "captureStateUpdate",
            "id": "1",
            "triggeredAt": "1970-01-01T00:00:00Z",
            "additionalData": {
                "state": "<CAPTURE_STATE>",
                "paymentType": "<PAYMENT_TYPE>",
                "captureInfo": {
                    "amount": 10000,
                    "captureId": "12345",
                    "currency": "SEK",
                    "paymentId": "987652"
                }
            }
        }
    ]
}

Where the possible values of <CAPTURE_STATE> are:

  • CAPTURE_DONE

  • ERROR

  • CAPTURE_DECLINED

  • CAPTURE_COOLDOWN

where CAPTURE_DECLINED is:

for payments that cannot be captured anymore as the card is declined from the payment gateway. The actionable step here, for you as the merchant, is to contact the customer to pay via other means.

where CAPTURE_COOLDOWN is:

In rare occasions the result of a capture operation can be a soft decline, with the possibility of recovering. A retry could be performed later but is not required as no money has been moved. Most notable cooldowns are for acquirer Nets and Forbrugsforeningen cards. Partial captures need to be 24 hours apart.

When retrying a new idempotency key must be used as it is a separate transaction.

And the possible values of <PAYMENT_TYPE> are:

  • CREDIT_CARD

  • MOBILEPAY

  • VIPPS

refundStateUpdate

Triggers after an update occurs to a refund that was initiated.

{
    "events": [
        {
            "event": "refundStateUpdate",
            "id": "1",
            "triggeredAt":"1970-01-01T00:00:00.000000+00:00",
            "additionalData": {
                "state":"<REFUND_STATE>",
                "refundType":"<REFUND_TYPE>",
                "refundInfo": {
                    "paymentId": "3980",
                    "refundId": "ade057da-3756-4419-a805-11d6728d526d",
                    "amount": 1000,
                    "currency": "SEK",
                    "errorMessage": "<REFUND_ERROR_MESSAGE>"
                }
            }
        }
    ]
}

Where the possible values of <REFUND_STATE> are:

  • PAID

  • ERROR

And the possible values of <REFUND_TYPE> are:

  • SWISH

Where <REFUND_ERROR_MESSAGE> is:

  • reason provided by the payment provider for the failure of the refund.

  • null otherwise.

purchaseInstalmentFirstReminder

Triggered when three days have passed since an instalment payment was due and the expected amount hasn’t been paid.

{
    "events": [
        {
            "event": "purchaseInstalmentFirstReminder",
            "id": "1",
            "triggeredAt": "1970-01-01T00:00:00Z",
            "additionalData": {
                "instalment": {
                    "dueDate": "1970-01-01T00:00:00Z",
                    "amountDue": 13000,
                    "amountPaid": 0,
                    "serviceFee": 0,
                    "reminderFee": 3000,
                    "currency": "SEK",
                    "partNumber": 1,
                    "paymentMethod": "SWISH"
                },
                "purchase": {
                    "id": "2",
                    "merchantReference": "b11511e0-048a-481f-b61d-9b8d6a91cbb6",
                    "createdAt": "1970-01-01T00:00:00Z"
                },
                "customer": {
                    "id": "3",
                    "firstName": "Haskell",
                    "lastName": "Curry",
                    "email": "[email protected]"
                }
            }
        }
    ]
}

purchaseInstalmentFinalReminder

Triggered when ten days have passed since an instalment payment was due and the expected amount hasn’t been paid.

{
    "events": [
        {
            "event": "purchaseInstalmentFinalReminder",
            "id": "1",
            "triggeredAt": "1970-01-01T00:00:00Z",
            "additionalData": {
                "instalment": {
                    "dueDate": "1970-01-01T00:00:00Z",
                    "amountDue": 16000,
                    "amountPaid": 0,
                    "serviceFee": 0,
                    "reminderFee": 6000,
                    "currency": "SEK",
                    "partNumber": 1,
                    "paymentMethod": "SWISH"
                },
                "purchase": {
                    "id": "2",
                    "merchantReference": "b11511e0-048a-481f-b61d-9b8d6a91cbb6",
                    "createdAt": "1970-01-01T00:00:00Z"
                },
                "customer": {
                    "id": "3",
                    "firstName": "Alonzo",
                    "lastName": "Church",
                    "email": "[email protected]"
                }
            }
        }
    ]
}

purchaseStateUpdate

Triggers at various points during the purchase flow of a customer.

{
    "events": [
        {
            "event": "purchaseStateUpdate",
            "id": "1",
            "triggeredAt": "1970-01-01T00:00:00Z",
            "additionalData": {
                "merchantReference": "b11511e0-048a-481f-b61d-9b8d6a91cbb6",
                "purchaseId": "2",
                "state": "<NEW_STATE>"
            }
        }
    ]
}

Where the possible values of <NEW_STATE> are:

  • WAITING_FOR_CAPTURE

  • CANCELLED

  • REJECTED

  • RETURNED

Queued notifications

The webhook notifications will be considered unsuccessful if the server receiving the webhook can’t be reached or does not reply with the expected acknowledgement within 10 seconds. In that case the undelivered notifications will be queued up and retried at a later time. The time between attempts will increase exponentially with subsequent failures according to the following schedule.

  • 2 minutes

  • 5 minutes

  • 10 minutes

  • 30 minutes

  • 1 hours

  • 2 hours

  • 4 hours

  • 8 hours (repeat here until 7 days have passed)

If the notifications have not been successfully delivered after seven days of trying then webhook notifications will be disabled. To re-enable the webhook notifications you need to Activate webhook notifications again.