Skip to main content

Webhooks

The /webhooks endpoint allows to create, list and modify webhooks. It requires authentication.

Webhook format

{
"url": "https://example.com/my-webhooks",
"secret": "<your webhook secret>",
"types": [
"charge.processing",
"charge.pending",
"charge.success",
"charge.failure",
"charge.late",
"charge.timeout",
"charge.overpaid",
"charge.underpaid",
"charge.resolved",
"charge.unsettled"
],
"organizationId": "<organization id>"
}

The types property can contain an array of charge.<status name> where status name is any of the charge status code names.

Listening to Webhook events

Creating a Webhook endpoint on your server is really no different from creating any other page on your website. Of course things will be different depending on which language or framework you're building your application with.

Verifying webhooks

caution

Only charges with a statusCode that is greater than, or equal to, 300 and less than 400 are considered successful so you should only mark orders as paid or send out a customer's order if a charge has code between those values.

Webhooks created through the API are verified by calculating a digital signature. Each webhook request includes a hex-encoded X-Signature header, which is generated using the webhooks shared secret along with the data sent in the request.

Webhooks created through the dashboard are verified using the secret displayed in the Webhooks section of the Dashboard.

To verify that the request came from our API, compute the HMAC digest according to the following algorithm and compare it to the value in the X-Signature header. If they match, then you can be sure that the webhook was sent from us.

Note: If you're using a Rack based framework such as Ruby on Rails or Sinatra, then the header you are looking for is HTTP_X_SIGNATURE.

Here are a few examples:

require 'rubygems'
require 'openssl'
require 'sinatra'

# Your webhook secret, viewable from the dashboard
WEBHOOK_SECRET = 'YOUR WEBHOOK SECRET'

helpers do
# Compare the computed HMAC digest based on the shared secret and the request contents
# to the reported HMAC in the headers
def verify_webhook(data, hmac_header)
calculated_hmac = OpenSSL::HMAC.hexdigest('sha1', WEBHOOK_SECRET, data)
ActiveSupport::SecurityUtils.secure_compare(calculated_hmac, hmac_header)
end
end

# Respond to HTTP POST requests sent to this web service
post '/webhook' do
request.body.rewind
data = request.body.read
verified = verify_webhook(data, env["HTTP_X_SIGNATURE"])

puts "Webhook verified: #{verified}"
status 200
body ''
json_data = JSON.parse(data)
status_code = json_data[:data][:statusCode]
if statusCode >= 300 && statusCode < 400
# Payment was successful, send out order, mark as paid, etc.
end
end

Responding to an event

In order for us to acknowledge that you have received the POST request, your server should respond with a 200 HTTP status. If your server responds with a 400 or higher HTTP status code we'll retry sending the POST request up to 20 times every five minutes.

Webhook payload

The webhook's POST request payload contains the charge itself. Here's what a successful charge looks like.

info

If you'd like to learn more about the different status codes check the onChargeUpdated callback section.

{
"id": "1234",
"type:": "charge.success",
"data": {
"id": "9a4f03cb-1948-4780-81ba-b8a53a7f6468",
"amount": "5",
"metadata": {
"firstName": "John",
"lastName": "Doe",
"email": "john@company.com"
},
"createdAt": "2018-08-17T18:43:11.808Z",
"expiresAt": "2018-08-17T18:58:11.733Z",
"description": null,
"statusCode": 300,
"redirectUrl": null,
"status": "success",
"currency": "USD",
"organization": "pk_viqah4squsnuw6p0e1ue",
"timeline": [
{
"id": "4fd18c22-98e3-4200-93a1-8fe85ba0526d",
"time": "2018-08-17T18:43:11.808Z",
"type": "CHARGE_CREATED",
"status": "pending"
},
{
"id": "60aa266c-b5ad-4ca9-ba5b-cd59593c0e46",
"time": "2018-08-17T18:43:17.884Z",
"type": "ETHEREUM_TRANSACTION_CREATED",
"status": "processing"
},
{
"id": "79690a0e-b199-4ff5-bc06-74311aea80e5",
"time": "2018-08-17T18:43:49.051Z",
"type": "ETHEREUM_TRANSACTION_CONFIRMED",
"status": "processing"
},
{
"id": "d3937d73-def9-4015-acaf-8f109c87a67d",
"time": "2018-08-17T18:44:35.822Z",
"type": "ETHEREUM_TRANSACTION_CONFIRMED",
"status": "processing"
},
{
"id": "0fbfd113-7237-4468-9d5d-79c07136e2ef",
"time": "2018-08-17T18:44:54.033Z",
"type": "ETHEREUM_TRANSACTION_CONFIRMED",
"status": "success"
}
],
"payments": {
"ethereum": {
"transactions": [
{
"hash": "0xdf12ce66a4a2b54b3465c6f5394963cd759dab24142f339e4b675ad7d73dd6fa",
"amount": "0.01743",
"confirmations": 3
}
],
"requiredConfirmations": 3,
"balance": "0.01743",
"pending": "0",
"outstanding": "0"
}
},
"quotes": {
"ethereum": {
"id": "ccceb362-2c11-4a08-ba3c-bbb60ba46110",
"rate": "0.003486",
"amount": "0.01743",
"index": "205",
"extraId": null,
"status": "ACTIVE",
"address": "0x9617fa5ff408dfc0115e0ae1532fa9e75428de61",
"currency": "ETH"
}
}
}
}