Webhooks

Introduction

Generally, when you make a request to an API endpoint, you expect to get a near-immediate response. However, some requests may take a long time to process, leading to timeout errors. To prevent a timeout error, a pending response is returned. Since your records need to be updated with the final state of the request, you need either of the following:

  1. Poll

  2. Use webhooks to receive events

Webhooks vs Polling

Polling requires making a GET request at regular intervals to get the final status of a request. For example, when a customer makes a payment for a transaction, you keep making a request for the transaction status until you get a successful transaction status.

With webhooks, the resource server, Juice in this case, sends updates to your server when the status of your request changes. The change in status of a request is known as an event. You’ll typically listen to these events on a POST endpoint called your webhook URL.

We recommend that you use webhook to provide value to your customers over using polling. We recommend you use the polling endpoints on occasions where you need to verify a payment or if your system has been down for an extended duration. With webhooks, ensure your webhook endpoint processes events in an idempotent fashion to avoid duplicate processing.

Creating a Webhook URL

A webhook URL is simply a POST endpoint that a resource server sends updates to. The URL needs to parse a JSON request and return a 200 OK:

When your webhook URL receives an event from us, it needs to parse and acknowledge the event. Acknowledging an event means returning a 200 OK response in the HTTP header. Without a 200 OK in the response header, we’ll keep sending events for the next 48 hours at specific intervals: Adding webhook URLs

PATCH /accounts/settings

Sample Request

{
  "require_mfa": true,
  "webhook_urls": [
    "string"
  ]
}

Sample Response

{
  "data": {
    "address": {
      "city": "Jory",
      "country": "EGY",
      "line1": "1 swift way ",
      "line2": "",
      "state": "atik",
      "zip_code": "10923"
    },
    "email": "user@example.com",
    "id": "string",
    "name": "string",
    "phone_number": "string",
    "status": "active",
    "type": "business",
    "settings": {
      "logo": "string",
      "require_mfa": true,
      "webhook_urls": [
        "string"
      ]
    }
  }
}

Verify event origin

Since your webhook URL is publicly available, you need to verify that events originate from Juice and not a bad actor. The two ways to ensure events to your webhook URL are from Juice are:

  1. Checksum validation

  2. IP whitelisting

Checksum validation

We have added a checksum attribute to every webhook payload.

Example payload below

{
  "checksum": "32762AE880695AE7343A649CB9C36CA6FF83AA258A139804AEF7D73B421DE097",
  "data": {
    "card_id": "81817411-9ffd-42ba-8bc8-f407b5cef9d9",
    "amount": 1000,
    "reference": "b070b0d2-e394-4783-a6f0-f10ccb3cae89",
    "currency": "USD"
  },
  "event": "card.transaction"
}

To calculate the checksum we've used the following algorithm

  • event|json_encoded_data

card.transaction|{"amount":1000, "card_id": "81817411-9ffd-42ba-8bc8-f407b5cef9d9", "currency": "USD", "reference": "b070b0d2-e394-4783-a6f0-f10ccb3cae89"}

The encoded data should exclude the checksum attribute and should be in alphabetical order. The checksum validation will fail if the encoded data is rendered in the wrong order. See example:

{"currency": "USD", "amount":1000, "card_id": "81817411-9ffd-42ba-8bc8-f407b5cef9d9", "reference": "b070b0d2-e394-4783-a6f0-f10ccb3cae89"}

Notice: The encoded data is the same, but the order in which the string is generated is not alphabetical. This checksum validation will fail.

  • create an HMAC SHA-256 hex-encoded hash, using the id of the business receiving the request as the hash key

In order to validate the checksum, you will need to repeat the operation on your system, using your id as the hash key. If your checksum matches ours, you can be certain that the request originated from Juice.

IP whitelisting

Using this method, you can accept requests from specific IP addresses to access your webhook URL, effectively blocking others.

Juice will send webhooks exclusively from the following IP address:

68.183.219.141 167.71.50.238 167.71.57.22 164.92.131.158 167.172.191.189 134.209.237.227

You should whitelist this IP address and consider requests from other IP addresses a counterfeit.

Go live checklist

After creating your webhook URLs successfully, you need to check this list for an optimal experience

  1. Ensure your webhook URL is publicly available online (localhost URLs cannot receive events)

  2. If using .htaccess kindly remember to add the trailing / to the URL

  3. Test your webhook to ensure you’re getting the JSON body and returning a 200 OK HTTP response

  4. If your webhook function has long-running tasks, you should first acknowledge receiving the webhook by returning a 200 OK before proceeding with the long-running tasks

  5. If we don’t get a 200 OK HTTP response from your webhooks, we flag it as a failed attempt.

  6. Ensure your webhook URL endpoints are idempotent.

Supported events

Here are the events we currently raise. We would add more to this list as we hook into more actions in the future.

In the sandbox environment, successful transactions will remain pending. Only failed transaction events will be propagated.

  • payment.session.created

{
    "checksum": "",
    "event": "payment.session.created",
    "data":{
            "id": ,
            "currency": ,
            "amount": ,
            "description": ,
            "payment_method": ,
            "status": "failed",
            "cancellation_reason": ,
            "date": ,
            "mode": ,
            "customer": {
                "first_name": ,
                "last_name": ,
                "email": ,
                "billing_address": {
                    "line1": ,
                    "line2": ,
                    "city": ,
                    "state": ,
                    "zip_code": ,
                    "country": ,
                }
            },
            "reference": ,
            "metadata": {
                
            }
        }
}
  • payment.session.succeeded

{
  "checksum": "7b27d802db3b0b7c1fd3f52d1c7cfcbf11ea9a2b7987ba7c7bbe4484b36beaa8",
  "data": {
    "amount": 500,
    "callback_urls": {},
    "cancellation_reason": null,
    "channel_reference": "255797522",
    "collection_mode": null,
    "correlation_id": "8015f012-8fe1-4a1d-8bd1-faf20e44f29e",
    "currency": "CAD",
    "customer": {
      "billing_address": {
        "city": "Burlington",
        "country": "CA",
        "line1": "1820 Walkers Line",
        "state": "Ontario",
        "zip_code": "L7M 4v4"
      },
      "email": "",
      "first_name": "",
      "id": "c2d00b82-3e98-4121-89ac-319cf83d6126",
      "last_name": "",
      "phone_number": "",
      "type": "individual"
    },
    "date": "2024-09-13T13:26:46.615348Z",
    "description": "interac deposit",
    "fee": {
      "charged_to": "merchant",
      "fee": {
        "amount": 2,
        "currency": "CAD"
      }
    },
    "id": "99edd31a-6d66-45d7-b977-0b348f5a2e74",
    "merchant": {
      "address": {
        "city": "Vancouver",
        "country": "CA",
        "line1": "422 Richards St, Suite 170, Vancouver, BC, V6B2Z4, Canada.",
        "line2": null,
        "state": "Vancouver",
        "zip_code": "V6B2Z4"
      },
      "email": "",
      "id": "3397665b-16da-4fca-8bbb-4c8e105cdad4",
      "mcc": "5997",
      "name": "",
      "phone": "+2348129897889"
    },
    "metadata": null,
    "mode": "live",
    "payer": null,
    "payment_method": null,
    "provider_id": "255797522",
    "reference": "100000000362693191",
    "status": "success",
    "transaction_id": null,
    "type": "payin"
  },
  "event": "payment.session.succeeded"
}
  • payment.session.failed

{
   "checksum":"7ff8316b782cce535c0cd1f08a64b3be4a54bd05b121ddaf1d4a7e31d8f05461",
   "data":{
      "amount":500,
      "callback_urls":{},
      "cancellation_reason":"Failed to create transaction",
      "channel_reference":"TdksZJn5LpLbKotWQ29cXR",
      "collection_mode":null,
      "correlation_id":"842b3b10-7035-11ef-9032-32ea6bcb29e7",
      "currency":"CAD",
      "customer":{
         "billing_address":{
            "city":"calgary",
            "country":"CA",
            "line1":"11 Parklane",
            "state":"alberta",
            "zip_code":"AGL588JM"
         },
         "email":"",
         "first_name":"",
         "id":"8fb5e18b-0558-4c1e-8307-7f5691716234",
         "last_name":"",
         "phone_number":"",
         "type":"individual"
      },
      "date":"2024-09-11T12:01:02.512190Z",
      "description":"",
      "fee":null,
      "id":"8430781e-7035-11ef-9ac9-32ea6bcbbvhe7",
      "merchant":{
         "address":{
            "city":"Vancouver",
            "country":"CA",
            "line1":"422 Richards St, Suite 170, Vancouver, BC, V6B2Z4, Canada.",
            "line2":null,
            "state":"Vancouver",
            "zip_code":"V6B2Z4"
         },
         "email":"",
         "id":"3397665b-16da-4fca-86eb-4c8e105cbvd4",
         "mcc":"5997",
         "name":"",
         "phone":""
      },
      "metadata":{},
      "mode":"live",
      "payer":null,
      "payment_method":{
         "answer":"",
         "beneficiary_type":"personal",
         "currency":"CAD",
         "email":"",
         "first_name":"",
         "id":"4891ab63-9b07-4f44-8f00-3715f23a4245",
         "last_name":"",
         "phone_number":"",
         "question":"",
         "type":"interac"
      },
      "provider_id":null,
      "reference":"TXN_1725_6644_024_33_333o_i531_8671_3399_uyuu",
      "status":"failed",
      "transaction_id":null,
      "type":"payout"
   },
   "event":"payment.session.failed"
}

Sample Response [Seccessful Desposit]

{
  "checksum": "",
  "event": "deposit.received",
  "data": {
    "currency": "NGN",
    "amount": 10000,
    "occured_at": "",
    "sender": "Ope Praise",
    "recipient": {
      "name": "Walter-Upton",
      "email": "alisha.schneider@hotmail.com"
    },
    "beneficiary": {
      "account_name": "Bennie Klocko",
      "account_number": "123456789"
    },
    "reference": ""
  }
}

Last updated