Webhook Notifications
How to use SphereOne webhooks for real-time payment status updates.
SphereOne uses webhooks to notify your application of payment status updates. By creating and configuring an endpoint for our notifications to be sent to, your application will be able to handle real-time updates regarding users' payments. This webhook URL can be configured on the SphereOne Merchant Dashboard settings page.
We'll send a JSON payload containing a paymentStatus
and a transactionId
(the same ID received after creating a charge) to your configured webhook URL when we begin processing the payment. The paymentStatus
can be one of the following values: PROCESSING
, SUCCESS
, or FAILURE
. In case of a payment failure, we'll also include an error
property containing the reason for the failure.
Webhook Payload Examples
{
"paymentStatus": "PROCESSING",
"transactionId": "0UzDE7FWzI1yGdMTQsIQ",
"error": null
}
{
"paymentStatus": "SUCCESS",
"transactionId": "0UzDE7FWzI1yGdMTQsIQ",
"error": null
}
{
"paymentStatus": "FAILURE",
"transactionId": "67wCM3w9eJb2wN9VS6db",
"error": "The minimum amount to transfer is $1"
}
Secure Webhooks
We will include an HMAC signature in the request header x-sphereone-signature
, which will allow you to confirm that the request is indeed from us. To verify this, you will need to create a signature by matching the payload of the request sent by the webhook with your webhook secret. You can obtain your webhook secret from the SphereOne Merchant Dashboard in the same section where the webhook URL is set.
To verify the webhook signature, you can refer to the following example.
import crypto from 'crypto';
app.post('/webhook-endpoint', (req, res) => {
const payload = req.body;
const receivedSignature = req.headers['x-sphereone-signature'];
const secret = 'your_secret_key'; // Replace with the webhook secret from the merchant dashboard
const calculatedSignature = crypto.createHmac('sha256', secret).update(JSON.stringify(payload)).digest('hex');
if (receivedSignature === calculatedSignature) {
// The webhook is verified
} else {
// The webhook is not verified
}
});
Webhook Endpoint Implementation Examples
Below, we provide two examples of how you can set up a webhook endpoint to receive payment status updates in real time. In each example, we use the paymentStatus
received from the webhook payload to determine the appropriate action, and the transactionId
to fetch and update the corresponding transaction record in our database. The first example uses a Firebase Cloud Function with a Firestore database, while the second example utilizes an Express server with Prisma ORM for a PostgreSQL database.
// FIREBASE CLOUD FUNCTION - FIRESTORE
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import cors from "cors";
const db = admin.firestore();
export const webhooks = functions.https.onRequest(async (req, res) => {
cors({origin: true})(req, res, async () => {
try {
if (req.method !== "POST") throw new Error("Method not allowed");
const {paymentStatus, transactionId, error} = req.body;
if (!paymentStatus || !transactionId) res.status(400).json({msg: "Missing information"});
if (paymentStatus === "SUCCESS") {
// Get transaction details from database
const transaction = (await db.doc(`transactions/${transactionId}`).get()).data();
if (transaction === undefined) throw new Error("Transaction not found");
const nftUid = transaction.nftUid;
const userUid = transaction.userUid;
// Update transaction status
await db.doc(`transactions/${transactionId}`).set({
status: "SUCCESS",
}, {merge: true});
// Assign nft to user
await db.doc(`nfts/${nftUid}`).set({
ownerUid: userUid,
}, {merge: true});
} else if (paymentStatus === "FAILURE") {
// Handle error
await db.doc(`transactions/${transactionId}`).set({
status: "FAILURE",
error: error ?? null,
}, {merge: true});
} else {
// Just update status
await db.doc(`transactions/${transactionId}`).set({
status: paymentStatus,
error: error?? null,
}, {merge: true});
}
return res.status(200).send("OK");
} catch (error) {
return res.status(400).send(error?.message);
}
});
});
// EXPRESS - PRISMA POSTGRESQL
import express from "express";
import cors from "cors";
import { PrismaClient } from "@prisma/client";
const app = express();
const prisma = new PrismaClient();
app.use(cors());
app.use(express.json());
app.post("/webhooks", async (req, res) => {
try {
const { paymentStatus, transactionId, error } = req.body;
if (!paymentStatus || !transactionId)
res.status(400).json({ msg: "Missing information" });
if (paymentStatus === "SUCCESS") {
// Get transaction details from database
const transaction = await prisma.transaction.findUnique({
where: { id: transactionId },
});
if (!transaction) throw new Error("Transaction not found");
const { nftUid, userUid } = transaction;
// Update transaction status
await prisma.transaction.update({
where: { id: transactionId },
data: { status: "SUCCESS" },
});
// Assign nft to user
await prisma.nft.update({
where: { id: nftUid },
data: { ownerUid: userUid },
});
} else if (paymentStatus === "FAILURE") {
// Handle error
await prisma.transaction.update({
where: { id: transactionId },
data: { status: "FAILURE", error: error ?? null },
});
} else {
// Just update status
await prisma.transaction.update({
where: { id: transactionId },
data: { status: paymentStatus, error: error ?? null },
});
}
return res.status(200).send("OK");
} catch (error) {
return res.status(400).send(error?.message);
}
});
Updated 3 months ago