Skip to main content

Webhooks v1.1

Malga uses the webhooks service to notify your system about events occurring in our platform. Through webhooks you can update your system whenever an important event happens, such as updating the status of charges to confirm or cancel a certain payment.

Looking for version 1.0? See here

Basic flow to receive notifications via webhooks:

  • Create a service with an endpoint accessible within your system to receive event notification requests made by Malga.
  • Register your endpoint with Malga by creating a webhook to receive notifications of desired events.
  • Malga will send an HTTP request to your endpoint with the modified object data whenever the given event registered in your webhook happens.

Creating a webhook

Create and manage webhooks using the Webhook Service.

curl --location --request POST 'https://api.malga.io/v1/webhooks' \
--header 'X-Client-Id: <YOUR_CLIENT_ID>' \
--header 'X-Api-Key: <YOUR_SECRET_KEY>' \
--header 'Content-Type: application/json' \
--data-raw '{
"event": "transaction.authorized",
"endpoint": "https://enuqkxq2lu8be0y.m.pipedream.net",
"version": 1,
"status": true
}'

< HTTP/2 201
{
"id": "31c142ad-4c30-4964-ba24-2df0f2bbb745",
"clientId": "cc0b1e41-2936-45c5-947f-93995ffcdc00",
"event": "transaction.authorized",
"endpoint": "https://enuqkxq2lu8be0y.m.pipedream.net",
"version": 1,
"status": true,
"createdAt": "2021-07-06T21:03:36.590Z",
"updatedAt": "2021-07-06T21:03:36.590Z"
}

Notification event sent

Many of the events that occur in your Malga integration are synchronous, and you receive a direct response to your request, as in the cases of creating a customer, creating a card, etc.

However, in certain cases, the response you receive after making a request does not include the final status of that object, making it necessary to register a webhook to receive asynchronous responses from Malga's API, keeping your system up to date. This happens mainly in cases of billing through PIX and Invoices/Payment slips, notification of suspected fraud, financial clearance of transactions, among others.

When a certain event occurs, Malga creates an event object which is sent through an HTTP request to your registered endpoint. The event is immutable within the Malga's notification structure, meaning that the data of the object that has changed is saved along with the event, representing the state of the object immediately after the event that changed it.

Example of event notification request sent by Malga to your endpoint:

> POST <ENDPOINT_URL> HTTP/2
> Host: <ENDPOINT_HOST>
> User-Agent: axios/0.21.1
> Accept: application/json, text/plain, */*
> Content-Type: application/json
> x-idempotency-key: 5616b19e-4d99-4bd3-b415-4990e5cab4f4
> HTTP/2

{
"id": "5616b19e-4d99-4bd3-b415-4990e5cab4f4",
"apiVersion": "1",
"object": "transaction",
"event": "authorized",
"createdAt": "2021-07-05T18:56:08.672Z"
"data": {
"id": "242b9be8-cd60-461d-af27-f31e3d6e3fb7",
"updatedAt": "2021-07-05T18:56:08.247Z",
"createdAt": "2021-07-05T18:56:08.247Z",
"idempotencyKey": null,
"requestId": null,
"amount": 1500,
"originalAmount": 1500,
"installments": 1,
"clientId": "cc0b1e41-2936-45c5-947f-93995ffcdc00",
"description": null,
"statementDescriptor": "Pedido #231 loja joão",
"status": "authorized",
"capture": true,
"fee": null,
"feeAmount": null,
"transactionRequests": [{
"id": "87c3973c-6a8d-40a5-8b3b-f6e89a8393ab",
"updatedAt": "2021-07-05T18:56:08.648Z",
"createdAt": "2021-07-05T18:56:08.255Z",
"idempotencyKey": "fdd134fa-f79e-4164-b635-a6510908e1a2",
"requestId": null,
"providerId": "367b118f-a9be-421b-bb61-5df4261df634",
"providerType": "STRIPE",
"transactionId": "ch_3JYE7MHjGFBGEeiP0lfTD3Ob",
"amount": 1500,
"authorizationCode": "486677",
"authorizationNsu": "d8d230ce-7222-4ca6-b08f-135289588bc8",
"responseCode": "1",
"requestStatus": "success",
"requestType": "authorization",
"responseTs": "377ms",
}]
}
}

Best practices to receive notifications

Attempts and reattempts to send notifications

Malga will attempt to send a given notification to your webhook, in case of problems processing the service, we will make new attempts to deliver the notifications escalating the interval between attempts.

To finish processing the notification, your service must return an HTTP STATUS 200 (OK) or 201 (CREATED) within the maximum waiting time, otherwise it will be understood that the endpoint did not receive it correctly and the event will be marked for reattempt.

EventInterval of the sendingsResponse timeout
Createdinstantly30 seconds
First attempt5 seconds5 seconds
Second attempt45 seconds5 seconds
Third attempt6 hours5 seconds
Fourth attempt2 days5 seconds
Fifth attempt4 days5 seconds
info

Malga keeps track of all notifications made, as well as the Request and Response information from the external server. After the maximum number of event delivery attempts is exceeded, we mark the message as lost and it is stored for future reprocessing upon request.

The URL of your webhook must be exposed (public) to the internet, so that the Malga platform can reach it and send the events.

Processing events, order and duplicity

When a certain event occurs, Malga then creates an event object which registers the object and the type of the update event as well as the date of occurrence. Each event has a unique identifier that must be used client-side to avoid duplicate processing, the identifier is sent in the event object in the request body and also in the request's x-idempotency-key header, of the same value.

The events are sent through an HTTP request to your endpoint exactly in the order they occurred in the Malga's system, but we recommend that you use the creation date of the event, also sent in the event object, to ensure a chronological order in the processing of client-side events. If you receive an event with a creation date lower than the creation date of another event already processed by your system, the data of the object sent in the event will be outdated, leaving it up to you to take action with this event or not.

Testing notification via webhooks

To test your integration with Malga's webhooks, you can develop your system directly or use a service like request.bin or pipedream.com to primarily validate the events you sent. Just generate a new endpoint in these services and register a webhook on Malga with the generated endpoint and all the events sent will be registered in these services for consultation and debugging.

In sandbox sandbox-api.malga.io you can manually update created transactions to authorized, voided and charged_back, useful to simulate desired chrage status and test your integration.

Request to update one transaction in sandbox environment

curl --location --request POST 'https://api.malga.io/v1/charges/<CHARGE_ID>' \
--header 'X-Client-Id: <YOUR_CLIENT_ID>' \
--header 'X-Api-Key: <YOUR_SECRET_KEY>' \
--header 'Content-Type: application/json' \
--data-raw '{
"status": "charged_back"
}'

Webhook Security

As of version 1.1 of the webhook, the Malga started to subscribe to all events to ensure the security of receiving the event.

We use a private key of type Ed25519 to sign the events. When registering the webhook, you receive the public key of the same type to verify that the signature sent matches the payload received.

Every event received must be verified and if the signature is not recognized, for security reasons, you must discard the event.

We send 2 headers:

  • X-Plug-Date containing the date the event was generated. The format follows the UTC Unix Timestamp standard.
  • X-Plug-Signature containing a 64-bit hexadecimal hash.

To prevent a reply attack make sure the event date (X-Plug-Date) is within your acceptance criteria. We recommend not accepting events longer than 5 minutes.

Now you must validate the signature (X-Plug-Signature). For this you will need to use 4 data, X-Plug-Date, X-Plug-Signature, payload and the public key (pubKey) (which is returned when creating from the webhook).

First you must concatenate the date with the body encoded to utf8, encrypt it with the public key of the created webhook, and validate the cryptogram received in the header signature with the generated cryptogram to verify integrity.

The algorithm for verifying the signature looks like the one stated below:

date = header['X-Plug-Date']
msg = "{date}\n{payload}"
sig_raw = header['X-Plug-Signature']
sig_byte = hex_to_bin(sig_raw)

EdDSA_signature_verify(msg, pubKey, sig_byte) --> bool

See more about EdDSA e Ed25519

Samples of how to validate the signature

Examples are available in this github repository

https://github.com/plughacker/plug-sample-signature-verify/

See a snippet of each language

const crypto = require("crypto");

module.exports = {
/**
* Example of a function that validates the payload signature
* @param {string} publicKey Key returned on webhook creation
* @param {string} payload Event sent by the plug. This information goes in the body http
* @param {number} signatureTime Time in unix timestamp that the event was subscribed to
* @param {string} signature Hash sha512
* @returns {bool}
*/
verify: function (publicKey, payload, signatureTime, signature) {
const payloadUtf8 = Buffer.from(payload, "utf-8").toString();
const signatureBuffer = Buffer.from(signature, "hex");
const data = Buffer.from(`${signatureTime}\n${payloadUtf8}`);
return crypto.verify(null, data, publicKey, signatureBuffer);
},
};

Supported events for notification via webhooks:

EventDescription
transaction.pendingEvent sent when the charge is registered and payment data is available
transaction.pre_authorizedEvent sent when the payment confirmation of the charge is recognized
transaction.authorizedEvent sent when the payment confirmation of the charge is recognized
transaction.failedEvent sent when the charge is denied by the financial institution before it has been authorized
transaction.canceledEvent sent when the charge is canceled after being authorized but not captured, without financial refund
transaction.voidedEvent sent when the charge is cancelled after it has been authorized and captured, creating a financial refund
transaction.charged_backEvent sent when the charge is cancelled after being disputed and/or not recognized by the cardholder
transaction.disputeEvent sent when a transaction related dispute is opened
transaction.dispute_closedEvent sent when a dispute is closed. If you get charged_back instead of dispute_closed, it means the customer won the dispute.
transaction.refund_pendingEvent sent when a chargeback is pending. This can happen on asynchronous streams like PIX.