Webhooks

Getting started with Webhooks

Webhooks are an essential part of your payment integration. They allow Revio to notify you about events on your account, such as a successful payment or a failed transaction.

A webhook URL is an endpoint on your server where you can receive notifications about such events. When an event occurs, we'll make a POST request to that endpoint, with a JSON body containing the event's details, including the event's type and the associated data.


When to use Webhooks

Webhooks are supported for all kinds of payment methods, but they're especially useful for methods and events that happen outside your application's control, such as:

  • getting paid via Card, Instant EFT or Debit Order
  • a customer being charged for their subscription (recurring payments)
  • a pending payment transitions to successful

These are all asynchronous actions—your application does not control them, so you won't know when they are completed unless we notify you or check later.

Setting up a webhook allows us to notify you when these payments are completed. Within your webhook endpoint, you can then:

  • Update a customer's membership records in your database when a subscription payment succeeds.
  • Email a customer when a subscription payment fails.
  • Update your order records when a pending payment status is updated to successful.

Webhook Retry logic

Our callbacks have a retry mechanism that uses exponential backoff.

The last retry attempt is made at +36h of the first webhook.


Webhook structure

{
  "id": "dd1d0995-1105-4086-ab81-40da2d831325",
  "due": 1663329928,
  "type": "purchase",
  "client": {
    "cc": [],
    "bcc": [],
    "city": "",
    "email": "[email protected]",
    "phone": "",
    "country": "",
    "zip_code": "",
    "bank_code": "",
    "full_name": "",
    "brand_name": "",
    "legal_name": "",
    "tax_number": "",
    "client_type": null,
    "bank_account": "",
    "personal_code": "",
    "shipping_city": "",
    "street_address": "",
    "shipping_country": "",
    "shipping_zip_code": "",
    "registration_number": "",
    "shipping_street_address": ""
  },
  "issued": "2022-09-16",
  "status": "paid",
  "is_test": true,
  "payment": {
    "amount": 100,
    "paid_on": 1663326625,
    "currency": "ZAR",
    "fee_amount": 0,
    "net_amount": 90,
    "description": "",
    "is_outgoing": false,
    "payment_type": "purchase",
    "pending_amount": 10,
    "remote_paid_on": 1663326625,
    "owned_bank_code": null,
    "owned_bank_account": null,
    "pending_unfreeze_on": 1663585825,
    "owned_bank_account_id": null
  },
  "product": "purchases",
  "user_id": null,
  "brand_id": "f880ebd4-04fb-4b88-8531-7ca148c93f2e",
  "order_id": null,
  "platform": "api",
  "purchase": {
    "debt": 0,
    "notes": "",
    "total": 100,
    "currency": "ZAR",
    "language": "en",
    "products": [
      {
        "name": "test",
        "price": 100,
        "category": "",
        "discount": 0,
        "quantity": "1.0000",
        "tax_percent": "0.00"
      }
    ],
    "timezone": "UTC",
    "due_strict": false,
    "email_message": "",
    "total_override": null,
    "shipping_options": [],
    "subtotal_override": null,
    "total_tax_override": null,
    "payment_method_details": {},
    "request_client_details": [],
    "total_discount_override": null
  },
  "client_id": null,
  "reference": "",
  "viewed_on": 1663326561,
  "company_id": "492c861c-a64f-424f-8edf-9165667dc526",
  "created_on": 1663326328,
  "event_type": "purchase.paid",
  "updated_on": 1663326625,
  "invoice_url": null,
  "checkout_url": "https://gate.revio.co.za/p/dd1d0995-1105-4086-ab81-40da2d831325/invoice/",
  "send_receipt": false,
  "skip_capture": false,
  "creator_agent": "",
  "issuer_details": {
    "website": "",
    "brand_name": "ACME Insurance",
    "legal_city": "",
    "legal_name": "ACME Insurance",
    "tax_number": "",
    "bank_accounts": [
      {
        "bank_code": "",
        "bank_account": ""
      }
    ],
    "legal_country": "ZA",
    "legal_zip_code": "",
    "registration_number": "",
    "legal_street_address": ""
  },
  "marked_as_paid": false,
  "status_history": [
    {
      "status": "created",
      "timestamp": 1663326328
    },
    {
      "status": "viewed",
      "timestamp": 1663326561
    },
    {
      "status": "pending_execute",
      "timestamp": 1663326621
    },
    {
      "status": "paid",
      "timestamp": 1663326625
    }
  ],
  "cancel_redirect": "",
  "created_from_ip": "102.65.48.194",
  "direct_post_url": null,
  "force_recurring": false,
  "recurring_token": null,
  "failure_redirect": "",
  "success_callback": "",
  "success_redirect": "",
  "transaction_data": {
    "flow": "payform",
    "extra": {
      "card_type": "debit",
      "card_brand": "mastercard",
      "masked_pan": "555555******4444",
      "card_issuer": "ciagroup",
      "expiry_year": 22,
      "expiry_month": 12,
      "cardholder_name": "Test",
      "card_issuer_country": "BR"
    },
    "country": "BR",
    "attempts": [
      {
        "flow": "payform",
        "type": "execute",
        "error": null,
        "extra": {
          "card_type": "debit",
          "card_brand": "mastercard",
          "masked_pan": "555555******4444",
          "card_issuer": "ciagroup",
          "expiry_year": 22,
          "expiry_month": 12,
          "cardholder_name": "Test",
          "card_issuer_country": "BR"
        },
        "country": "BR",
        "client_ip": "102.65.48.194",
        "fee_amount": 10,
        "successful": true,
        "payment_method": "mastercard",
        "processing_time": 1663326625
      }
    ],
    "payment_method": "mastercard"
  },
  "refundable_amount": 100,
  "is_recurring_token": false,
  "billing_template_id": null,
  "currency_conversion": null,
  "reference_generated": "RV2",
  "refund_availability": "all",
  "payment_method_whitelist": null
}
{
  "id": "3d6ae45d-1525-4b03-9206-0b6ff82bf6f1",
  "due": 1663327973,
  "type": "purchase",
  "client": {
    "cc": [],
    "bcc": [],
    "city": "",
    "email": "[email protected]",
    "phone": "",
    "country": "",
    "zip_code": "",
    "bank_code": "",
    "full_name": "",
    "brand_name": "",
    "legal_name": "",
    "tax_number": "",
    "client_type": null,
    "bank_account": "",
    "personal_code": "",
    "shipping_city": "",
    "street_address": "",
    "shipping_country": "",
    "shipping_zip_code": "",
    "registration_number": "",
    "shipping_street_address": ""
  },
  "issued": "2022-09-16",
  "status": "error",
  "is_test": true,
  "payment": null,
  "product": "purchases",
  "user_id": null,
  "brand_id": "f880ebd4-04fb-4b88-8531-7ca148c93f2e",
  "order_id": null,
  "platform": "api",
  "purchase": {
    "debt": 0,
    "notes": "",
    "total": 100,
    "currency": "ZAR",
    "language": "en",
    "products": [
      {
        "name": "test",
        "price": 100,
        "category": "",
        "discount": 0,
        "quantity": "1.0000",
        "tax_percent": "0.00"
      }
    ],
    "timezone": "UTC",
    "due_strict": false,
    "email_message": "",
    "total_override": null,
    "shipping_options": [],
    "subtotal_override": null,
    "total_tax_override": null,
    "payment_method_details": {},
    "request_client_details": [],
    "total_discount_override": null
  },
  "client_id": null,
  "reference": "",
  "viewed_on": 1663326689,
  "company_id": "492c861c-a64f-424f-8edf-9165667dc526",
  "created_on": 1663324373,
  "event_type": "purchase.payment_failure",
  "updated_on": 1663326852,
  "invoice_url": null,
  "checkout_url": "https://payments.revio.co.za/p/3d6ae45d-1525-4b03-9206-0b6ff82bf6f1/",
  "send_receipt": false,
  "skip_capture": false,
  "creator_agent": "",
  "issuer_details": {
    "website": "",
    "brand_name": "ACME Insurance",
    "legal_city": "",
    "legal_name": "ACME Insurance",
    "tax_number": "",
    "bank_accounts": [
      {
        "bank_code": "",
        "bank_account": ""
      }
    ],
    "legal_country": "ZA",
    "legal_zip_code": "",
    "registration_number": "",
    "legal_street_address": ""
  },
  "marked_as_paid": false,
  "status_history": [
    {
      "status": "created",
      "timestamp": 1663324373
    },
    {
      "status": "viewed",
      "timestamp": 1663326689
    },
    {
      "status": "pending_execute",
      "timestamp": 1663326850
    },
    {
      "status": "error",
      "timestamp": 1663326852
    }
  ],
  "cancel_redirect": "",
  "created_from_ip": "102.65.48.194",
  "direct_post_url": null,
  "force_recurring": false,
  "recurring_token": null,
  "failure_redirect": "",
  "success_callback": "",
  "success_redirect": "",
  "transaction_data": {
    "flow": "payform",
    "extra": {
      "card_type": "debit",
      "card_brand": "mastercard",
      "card_issuer": "ciagroup",
      "card_issuer_country": "BR"
    },
    "country": "",
    "attempts": [
      {
        "flow": "payform",
        "type": "execute",
        "error": {
          "code": "general_transaction_error",
          "message": "Unrecognized transaction error"
        },
        "extra": {
          "card_type": "debit",
          "card_brand": "mastercard",
          "masked_pan": "555555******4444",
          "card_issuer": "ciagroup",
          "expiry_year": 22,
          "expiry_month": 11,
          "cardholder_name": "test",
          "card_issuer_country": "BR"
        },
        "country": "BR",
        "client_ip": "102.65.48.194",
        "fee_amount": 20,
        "successful": false,
        "payment_method": "mastercard",
        "processing_time": 1663326852
      }
    ],
    "payment_method": ""
  },
  "refundable_amount": 0,
  "is_recurring_token": false,
  "billing_template_id": null,
  "currency_conversion": null,
  "reference_generated": "RV1",
  "refund_availability": "none",
  "payment_method_whitelist": null
}
{
  "id": "d1b8e94e-6908-44e0-9724-2a3d6240bd0b",
  "due": 1663931735,
  "type": "purchase",
  "client": {
    "cc": [],
    "bcc": [],
    "city": "",
    "email": "[email protected]",
    "phone": "",
    "country": "",
    "zip_code": "",
    "bank_code": "",
    "full_name": "Test",
    "brand_name": "",
    "legal_name": "",
    "tax_number": "",
    "client_type": null,
    "bank_account": "",
    "personal_code": "",
    "shipping_city": "",
    "street_address": "",
    "shipping_country": "",
    "shipping_zip_code": "",
    "registration_number": "",
    "shipping_street_address": ""
  },
  "issued": "2022-09-16",
  "status": "created",
  "is_test": true,
  "payment": null,
  "product": "billing_invoices",
  "user_id": "44744012-b260-4cf1-a910-f692c98e893f",
  "brand_id": "f880ebd4-04fb-4b88-8531-7ca148c93f2e",
  "order_id": null,
  "platform": "web",
  "purchase": {
    "debt": 0,
    "notes": "",
    "total": 1000,
    "currency": "ZAR",
    "language": "en",
    "products": [
      {
        "name": "Test",
        "price": 1000,
        "category": "",
        "discount": 0,
        "quantity": "1.0000",
        "tax_percent": "0.00"
      }
    ],
    "timezone": "UTC",
    "due_strict": false,
    "email_message": "",
    "total_override": null,
    "shipping_options": [],
    "subtotal_override": null,
    "total_tax_override": null,
    "payment_method_details": {},
    "request_client_details": [],
    "total_discount_override": null
  },
  "client_id": "b9ffc7ab-4eb9-4ec8-b300-0bc50b6ec894",
  "reference": "",
  "viewed_on": null,
  "company_id": "492c861c-a64f-424f-8edf-9165667dc526",
  "created_on": 1663327006,
  "event_type": "purchase.created",
  "updated_on": 1663327006,
  "invoice_url": "https://gate.revio.co.za/p/d1b8e94e-6908-44e0-9724-2a3d6240bd0b/invoice/",
  "checkout_url": "https://payments.revio.co.za/p/d1b8e94e-6908-44e0-9724-2a3d6240bd0b/",
  "send_receipt": true,
  "skip_capture": false,
  "creator_agent": "",
  "issuer_details": {
    "website": "",
    "brand_name": "ACME Insurance",
    "legal_city": "",
    "legal_name": "ACME Insurance",
    "tax_number": "",
    "bank_accounts": [
      {
        "bank_code": "",
        "bank_account": ""
      }
    ],
    "legal_country": "ZA",
    "legal_zip_code": "",
    "registration_number": "",
    "legal_street_address": ""
  },
  "marked_as_paid": false,
  "status_history": [
    {
      "status": "created",
      "timestamp": 1663327006
    }
  ],
  "cancel_redirect": "",
  "created_from_ip": "102.65.48.194",
  "direct_post_url": null,
  "force_recurring": false,
  "recurring_token": null,
  "failure_redirect": "",
  "success_callback": "",
  "success_redirect": "",
  "transaction_data": {
    "flow": "payform",
    "extra": {},
    "country": "",
    "attempts": [],
    "payment_method": ""
  },
  "refundable_amount": 0,
  "is_recurring_token": false,
  "billing_template_id": "4af4427f-ec97-4830-bc3a-6fcb1e0ab69a",
  "currency_conversion": null,
  "reference_generated": "RV3",
  "refund_availability": "none",
  "payment_method_whitelist": null
}

Webhook authentication

Payloads are signed using asymmetric A.K.A. public-key cryptography to guarantee the authenticity of delivered callbacks. Each callback delivery request includes an X-Signature header field. This field contains a base64-encoded RSA PKCS#1 v1.5 signature of the SHA256 digest of the request body buffer. The signature is generated by using the entire webhook JSON body.

You can obtain the public key for Webhookauthentication from Webhook.public_key of the corresponding Webhook.

You can obtain the public key for success callback authentication from Webhook public key.

Webhook callback payloads are signed using a dedicated key pair. You can obtain the public key from Webhook.public_key.

You can see how to verify the signature in C# below:

using var rsa = new RSACryptoServiceProvider();

// Import the public key
rsa.ImportFromPem(publicKey.ToCharArray());

// Read signature from the header value and convert to bytes
var signatureBytes = Convert.FromBase64String(signature);

// Verify the signature
success = rsa.VerifyData(requestObject, signatureBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

Please note that Revio is not responsible for any financial losses incurred due to not implementing payload signature verification.


Creating a webhook

Webhooks can be created via the Revio dashboard or by API.

For creating and modifying Webhooks, see the Webhook CRUD API specification.

To create a webhook on the Revio dashboard:

  1. Log in to your dashboard and click on Developers
  2. Navigate to Webhooks to create a new Webhook
  3. Remember to specify the eventsYou want to listen to

3454

Revio allows up to 8 webhooks shared between live and test modes.

📘

Pro tip

When testing, you can get an instant webhook URL by visiting webhook.site. This will allow you to inspect the received payload without having to write any code or set up a server.


What's next