Handle status changes and webhooks
When using the capture
↗ endpoint, a
Transaction
↗ is
returned if there was no error when processing the payment. However, the
payment may still be pending, and a few fail-safes should be put in place.
Why should I use webhooks
Transactions can get stuck in an in-between pending & captured states, which makes it crucial to set up a way to be notified when the status of an invoice and/or transaction gets updated.
Most of the time with payments done by credit card, the merchant will know instantly if the payment made it through. However, with most alternative payment methods this is not the case, and payments can sometimes take a few minutes to be completely processed. Because of this, it is not possible to synchronously tell the customer or the merchant if the payment completely made it through: the payment is still pending.
When a payment is done with a credit card, it is also possible that it was successfully processed but that the customer choses to chargeback the payment later. Handling webhooks will also make you able to handle this case and appropriately update your customer’s profile on your business.
Transaction statuses
During a transaction’s life, its status will change several times depending
on multiple factors. For instance, when a payment is placed and confirmed, the
transaction’s status will switch from pending
to completed
.
You may find the list of all the available transaction’s statuses below:
waiting |
No payment has been placed yet |
pending |
The payment is pending confirmation by the payment gateway |
authorized |
The payment was authorized but not yet captured |
pending-capture |
The payment gateway initiated a capture, but it wasn’t confirmed yet |
completed |
The payment was sucessfully completed |
failed |
The payment has been placed, but failed |
voided |
The payment was voided |
refunded |
The transaction was refunded (totally or partially) |
in-review |
The transaction is pending fraud review |
blocked |
The transaction was blocked by your blocking rules & by a fraud review |
retrieval-request |
The payment was previously completed but the customer does not recognize the payment and is asking for information |
fraud-notification |
The payment was previously completed but the bank is notifying you a chargeback might be on its way |
chargeback-initiated |
The payment was previously completed but the customer filled a dispute |
solved |
The previous dispute has been resolved in your favor |
reversed |
The previous dispute has been resolved in your customer’s favor |
Handling webhooks & status changes
First, we’ll have to create an endpoint on our online service that accepts
requests from the Internet. This is the address to which ProcessOut will
POST
JSON data to notify you of the new events. Any CSRF
protection
should also be removed from this endpoint to ensure the correct processing
of the webhook.
Handling the webhook data is very simple: ProcessOut will post the ID of the event that was fired, and we’ll just have to fetch its data from the ProcessOut’s API. ProcessOut does not directly send the whole data of the event to ensure that the merchants checks the legitimicy of the event by first calling the API.
# cURL cannot be used to handle webhooks
var ProcessOut = require("processout");
var client = new ProcessOut(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB");
// req is filled with the decoded json data from the request body
client.newEvent().find(req["event_id"]).then(function(event) {
// We may now access the event
var data = event.getData();
switch (data["name"]) {
case "transaction.captured":
// Successful payment
break;
case "transaction.authorized":
// Payment was authorized but not yet captured
break;
// ...
// Access data from within the event data:
// data["transaction"].getId();
default:
console.log("Unknown webhook action");
return;
}
}, function(err) {
// An error occured, most likely the event was coming from an
// untrusted source
});
import processout
client = processout.ProcessOut(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")
# req is filled with the decoded json data from the request body
event = client.new_event().find(req["event_id"])
data = event.data
if event.name == "transaction.captured":
# Successful payment
pass
elif event.name == "transaction.authorized":
# Payment was authorized but not yet captured
pass
# ...
# Access data from within the event data:
# data.transaction.id
else:
# Shouldn't be here..
print("Unknown webhook action")
require "processout"
client = ProcessOut::Client.new(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB")
# req is filled with the decoded json data from the request body
event = client.event.find(req.event_id)
data = event.data
if event.name == "transaction.captured"
# Successful payment
elsif event.name == "transaction.authorized"
# Payment was authorized but not yet captured
# ...
# Access data from within the event data:
# data.transaction.id
else
# Shouldn't be here..
print("Unknown webhook action")
end
<?php
$client = new \ProcessOut\ProcessOut(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB");
$reqRaw = trim(file_get_contents("php://input"));
$req = json_decode($reqRaw, true);
$event = $client->newEvent()->find($req["event_id"]);
$data = $event->getData();
switch($event->getName())
{
case "transaction.captured":
// Successful payment
break;
case "transaction.authorized":
// Payment was authorized but not yet captured
break;
// ...
// Access data from within the event:
// $data["transaction"]->getId();
default:
echo "Unknown webhook action"; exit();
}
import "github.com/processout/processout-go"
var client = processout.New(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x",
"key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB",
)
// ProcessOutWebhook is the data received from a webhook request.
type ProcessOutWebhook struct {
EventID string `json:"event_id"`
}
type TransactionEvent struct {
Transaction *processout.Transaction `mapstructure:"transaction"`
}
func handleProcessOutWebhooks(
w http.ResponseWriter,
r *http.Request,
) {
// Decode the webhook.
var webhook ProcessOutWebhook
defer r.Body.Close()
if err := json.NewDecoder(r.Body).Decode(&webhook); err != nil {
panic(err)
}
// Fetching the associated event.
event, err := client.NewEvent().Find(webhook.EventID)
if err != nil {
// Webhook not found, possibly injected by a malicious third-party
w.WriteHeader(http.StatusBadRequest)
return
}
switch *event.Name {
case "transaction.captured":
// Successful payment.
case "transaction.authorized":
// Payment was authorized but not yet captured.
// …
// Access data from within the Data field:
//
// var ev TransactionEvent
// if err := mapstructure.Decode(event.Data, &ev); err != nil {
// panic(err)
// }
// fmt.Println(*ev.Transaction.Amount, *ev.Transaction.Currency)
}
// Always return a HTTP 200 OK response to signal the event was received.
w.WriteHeader(http.StatusOK)
}
The full list of events can be found in our API reference
↗.
Wrapping up by setting the URL in your Dashboard
In order to receive webhooks, you will need to go to your
Dashboard ↗ › Events › and add a
new webhook endpoint. This endpoint will be called by our servers to notify
yours of the new Event. This endpoint should therefore made accessible for us
to POST
JSON data to it.
You should now be ready to handle any event!