Webhook endpoint

Listen to events in your Ophelos account on your webhook endpoints so your integration and automatically trigger reactions.

When building Ophelos integrations, you might want your applications to reiceve events as they occur in your Ophelos accounts, so that your back-end systems can execute actions accordingly.

Create a webhook endpoint to receive events via HTTPS. After you register a webhook event, Ophelos can push real-time event data to your application's webhook endpoint when events happen in your Ophelos account. Ophelos uses HTTPS to send webhook events to your application as a JSON payload that includes a Event object.

Receiving webhook events is particulary useful for listening to asynchronous events such as when payments are received, when a customer accepts a payment plan, or when debts are fully paid.

Getting started

To start receiving webhook events in your app, create and register a webhook endpoint:

  1. Create a webhook endpoint handler to receive event data POST requests.
  2. Register your endpoint within Ophelos using the API.
  3. Secure your webhook endpoint.

You can register and create one endpoint to handle several different event types at the same time, or setup individual endpoints for specific events.

1. Create a handler

Set up an HTTPS endpoint function that can accept webhook requests with a POST method. If you're still developing your endpoint function on your local machine, it can use HTTP. After it's publicly accessible, your webhook endpoint must use HTTPS.

Setup your endpoint function so that it:

  1. Handles POST request with a JSON payload consisting of an event object.
  2. Quickly returns successful status code (2XX) prior to any complex logic that could cause a timeout.

2. Test your handler

Before you go-live with your webhook endpoint function, we recommend that you test your application integration. You can do so by constructing event payloads in your favourite API request constructor.

3. Register your endpoint

After testing your webhook endpoint function, register the webhook endpoint's accessible URL using the API so Ophelos knows where to deliver etents. Registered webhook endpoints must be publicly accessible HTTPS URLs.

Webhook URL format

The URL format to register a webhook endpoint is:

https://<your-website>/<your-webhook-endpoint>

For example, if your domain is https://mycompanysite.com and the route to your webhook endpoint is /ophelos_webhooks, specity https://mycompanysite.com/ophelos_webhooks as the Endpoint URL.

Register a webhook endpoint within the Ophelos API

You can programmatically create webhook endpoints.

The following example creates an endpoint that notifies you when a payment is successful and a debt is updated.

curl --request POST \
     --url https://api.ophelos.dev/webhooks \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "enabled_events": [
    "payment.succeeded",
    "debt.updated"
  ],
  "url": "https://example.com/ophelos_webhook"
}
'
const url = 'https://api.ophelos.dev/webhooks';
const options = {
  method: 'POST',
  headers: {accept: 'application/json', 'content-type': 'application/json'},
  body: JSON.stringify({
    enabled_events: ['payment.succeeded', 'debt.updated'],
    url: 'https://example.com/ophelos_webhook'
  })
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));
require 'uri'
require 'net/http'

url = URI("https://api.ophelos.dev/webhooks")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["accept"] = 'application/json'
request["content-type"] = 'application/json'
request.body = "{\"enabled_events\":[\"payment.succeeded\",\"debt.updated\"],\"url\":\"https://example.com/ophelos_webhook\"}"

response = http.request(request)
puts response.read_body
<?php

$curl = curl_init();

curl_setopt_array($curl, [
  CURLOPT_URL => "https://api.ophelos.dev/webhooks",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => json_encode([
    'enabled_events' => [
        'payment.succeeded',
        'debt.updated'
    ],
    'url' => 'https://example.com/ophelos_webhook'
  ]),
  CURLOPT_HTTPHEADER => [
    "accept: application/json",
    "content-type: application/json"
  ],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.ophelos.dev/webhooks"

	payload := strings.NewReader("{\"enabled_events\":[\"payment.succeeded\",\"debt.updated\"],\"url\":\"https://example.com/ophelos_webhook\"}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("accept", "application/json")
	req.Header.Add("content-type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(string(body))

}
import requests

url = "https://api.ophelos.dev/webhooks"

payload = {
    "enabled_events": ["payment.succeeded", "debt.updated"],
    "url": "https://example.com/ophelos_webhook"
}
headers = {
    "accept": "application/json",
    "content-type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)

4. Secure your endpoint

You need to secure your integration by making sure your handler verifies that all your webhook request are generated by Ophelos.

Verify webhook signatures manually

The Ophelos-Signature header included in each signed event contains a timestamp and one or more signatures that you must verify. The timestamp is prefixed by t=, and each signature is prefixed by a scheme. Schemes start with v, followed by an integer. Currently, the only valid live signature scheme is v1.

Ophelos-Signature:
t=1492774577,
v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

⚠️

We provide newlines for clarity, but a real Ophelos-Signature header is on a single line.

Ophelos generates signatures using a hash-based message authentication code (HMAC) with SHA-256. To prevent downgrade attacks, ignore all schemes that aren't v1.

To create a manual solution for verifying signatures, you must complete the following steps:

  1. Extract the timestamp and signatures from the header
    Split the header using the , character as the separator to get a list of elements. Then split each element using the = character as the separator to get a prefix and value pair.
    The value of the t corresponds to the timestamp, and v1 corresponds to the signature (or signatures). You can discard all other elements.
  2. Prepare the signed_payload string
    The signed_payload string is created by concatenating:
    1. The timestamp (as a string)
    2. The character .
    3. The actual JSON payload (that is, the request body)
  3. Determine the expected signature
    Compute an HMAC with the SHA-256 hash function. Use the endpoint's signing secret as the key, and use the signed_payload string as the message.
  4. Compare the signatures
    Compare the signature (or signatures) in the header to the expected signature. For an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance.
    To protect against timing attacks, use a constant-time-string comparison to compare the expected signature to each of the received signatures.

Debug webhook integrations

Multiple types of issues can occur when delivering events to your webhook endpoint:

  • Ophelos might not be able to deliver an event to your webhook endpoint.
  • Your webhook endpoint might have an SSL issue.
  • Your network connectivity is intermittent.
  • Your webhook endpoint isn't reciving events that you expect to receive.

View event deliveries

To view all events that were triggered in your account, view the Webhook Events (https://crm.ophelos.dev/api/admin/webhook_events for staging) tab in.

Fix HTTP status codes

When an event displays a status code of 200, it indicates successful delivery to the webhook endpoint. You might also recieve a status code rather than 200. View the table below for a list of common HTTP status codes and recommended solutions

Pending webhook statusDescriptionFix
(unable to connect) ERRWe're unable to establish connection to the destination server.Make sure that your host domain is publicly accessible to the internet.
(302) ERR (or other 3xx status)The destination server attempted to redirect the request to another location. We consider redirect responses to webhook requests as failures.Set the webhook endpoint to the destination to the URL resolves by the redirect.
(400) ERR (or other 4xx status)The destination server can't or won't process the request. This might occur when the server detects an error (400), when the destination URL has access restrictions, (401,403), or when the destination URL doesn't exist (404)- Make sure that your endpoint is publicly accessible to the internet. - Make sure that your endpoint accepts POST HTTP method.
(500) ERR (or other 5xx statusThe destination server encountered an error while processing the request.Review your application’s logs to understand why it’s returning a 500 error.
(TLS error) ERRWe couldn’t establish a secure connection to the destination server. Issues with the SSL/TLS certificate or an intermediate certificate in the destination server’s certificate chain usually cause these errors. Ophelos requires TLS version v1.2 or higher.Perform an SSL server test to find issues that might cause this error.
(Timed out) ERRThe destination server took too long to respond to the webhook request.Make sure you defer complex logic and return a successful response immediately in your webhook handling code.