Webhooks
Configure webhook URLs to have thirdweb notify your backend when successful or failed events occur.
Use cases
- Update your database when a buyer purchases an NFT.
- Send an email to a buyer after their purchase succeeds.
- Inform your team in Slack/Discord when a payment or purchase failed.
Events
The following webhook events are supported.
Event | Description |
---|---|
transfer:succeeded | The NFT has been delivered to the buyer's wallet. |
transfer:failed | The NFT was unable to be delivered after multiple retries. Paper's engineering team is notified to resolve or refund this transaction. |
payment:succeeded | A buyer's payment has been successfully completed. |
payment:failed | A buyer's payment attempt has been rejected. Extra data fields may be available with information from our payment processor on the failure reason. |
payment:refunded | A buyer's payment has been refunded because the mint failed multiple attempts. Extra data fields may be available with the reason for the refund. |
payment:hold_created | This is only emitted if capturePaymentLater is set. A buyer's payment method has a pre-authorization hold created for the given amount. They have not been charged yet. You can capture this hold to complete their purchase, or cancel it. |
Request format
Paper will call your backend with an HTTPS POST
request:
Headers
Content-Type: application/json
X-Paper-Signature: <SIGNATURE_FROM_THIRDWEB>
Request body
{
"event": "transfer:succeeded",
"result": {
"id": "5bbbada7-e864-4dac-ae4b-0ee4967f55d8",
"checkoutId": "70e08b7f-c528-46af-8b17-76b0e0ade641",
"walletAddress": "0x2086Fcd5b0B8F4aFAc376873E861DE00c67D7B83",
"walletType": "Preset",
"email": "buyer@example.com",
"quantity": 1,
"paymentMethod": "BUY_WITH_CARD",
"networkFeeUsd": 0.02,
"serviceFeeUsd": 1.79,
"totalPriceUsd": 45.99,
"createdAt": "2022-08-22T19:15:09.755375+00:00",
"paymentCompletedAt": "2022-08-22T19:16:01.673+00:00",
"transferCompletedAt": "2022-08-22T19:16:18.024+00:00",
"claimedTokens": {
"collectionAddress": "0x965550329b91b7c703a527347b613E175f38872d",
"collectionTitle": "My First NFT",
"tokens": [
{
"transferHash": "0x076d1b496152efd2a97d0db1d558c681188a1a76a8a2c271a33e4c34cc1fa467",
"transferExplorerUrl": "https://polygonscan.com/tx/0x076d1b496152efd2a97d0db1d558c681188a1a76a8a2c271a33e4c34cc1fa467",
"tokenId": "262",
"quantity": 1
}
]
},
"title": "My First Paper Checkout",
"transactionHash": "0x076d1b496152efd2a97d0db1d558c681188a1a76a8a2c271a33e4c34cc1fa467",
"valueInCurrency": "0.05",
"currency": "ETH",
"metadata": {
"myAppUserId": "23a9fj2930gya0"
},
"mintMethod": { ... },
"eligibilityMethod": { ... },
"contractArgs": { ... },
}
}
{
"event": "transfer:succeeded",
"result": {
"id": "5bbbada7-e864-4dac-ae4b-0ee4967f55d8",
"checkoutId": "70e08b7f-c528-46af-8b17-76b0e0ade641",
"walletAddress": "0x2086Fcd5b0B8F4aFAc376873E861DE00c67D7B83",
"walletType": "Preset",
"email": "buyer@example.com",
"quantity": 1,
"paymentMethod": "BUY_WITH_CARD",
"networkFeeUsd": 0.02,
"serviceFeeUsd": 1.79,
"totalPriceUsd": 45.99,
"createdAt": "2022-08-22T19:15:09.755375+00:00",
"paymentCompletedAt": "2022-08-22T19:16:01.673+00:00",
"transferCompletedAt": "2022-08-22T19:16:18.024+00:00",
"claimedTokens": {
"tokens": [
{
"transferHash": "0x076d1b496152efd2a97d0db1d558c681188a1a76a8a2c271a33e4c34cc1fa467",
"transferExplorerUrl": "https://polygonscan.com/tx/0x076d1b496152efd2a97d0db1d558c681188a1a76a8a2c271a33e4c34cc1fa467",
"tokenId": "262",
"quantity": 1,
"from": "0xce6913CA121276E550b82844A08aCB4dfDc09178",
"collectionAddress": "0x965550329b91b7c703a527347b613E175f38872d",
"collectionTitle": "My First NFT"
}
]
},
"title": "My First Paper Checkout",
"transactionHash": "0x076d1b496152efd2a97d0db1d558c681188a1a76a8a2c271a33e4c34cc1fa467",
"valueInCurrency": "0.05",
"currency": "ETH",
"metadata": {
"myAppUserId": "23a9fj2930gya0"
},
"mintMethod": { ... },
"eligibilityMethod": { ... },
"contractArgs": { ... },
}
}
Usage
Provide a webhook handler URL
Webhooks are configured separately for testnet and production checkout on the dashboard. Webhook URLs must be publicly accessible https
endpoints.
Do not provide a localhost
URL to test your local server. We recommend testing your development server with a service like ngrok to serve a temporary public URL.
Please return a 2xx
response for unexpected or unused event types to prevent unnecessary retries.
Verify the signature header
To ensure the request came from thirdweb, each webhook request signs the payload and provides this signature in the X-Paper-Signature
header.
To verify this signature, create a SHA-256 HMAC hash with your API Secret Key as the secret and the body payload as the message (as a JSON-encoded string).
Example implementation
Here's a simplified HTTP handler in Next.js:
import { createHmac, timingSafeEqual } from "crypto";
const paperWebhookHandler = (req, res) => {
const apiKey = "2483b84a-..."; // Your thirdweb API Secret Key
// Get the provided signature.
const signature = req.headers["x-paper-signature"];
// Compute the expected signature.
const hash = createHmac("sha256", apiKey)
.update(JSON.stringify(req.body)) // {"event":"transfer:succeeded","result":{"id":...
.digest("hex");
// Confirm the provided signature matches.
if (!timingSafeEqual(Buffer.from(signature), Buffer.from(hash))) {
return res.status(400).send("Signature mismatch!");
}
switch (req.body.event) {
case "transfer:succeeded":
// Handle when an NFT was delivered.
case "transfer:failed":
// Handle when an NFT could not be delivered.
default:
// Ignore all other events and return 2xx.
}
return res.status(200).send("OK");
};
Test the webhook response
Use the Test webhook button to send a dummy payload to your webhook URL and see response status/body.
View recent webhook events
Select the List events button to view the recent webhook events, including the request body and response status/body from your backend. This view is useful to debug misconfigured webhook handlers.
FAQ
Why do I need to verify the signature header?
If your server is public, a bad actor can spoof a webhook request. Verifying the signature ensures the payload has not been changed. If a bad actor changes the webhook request body, the signature would not match the signed payload.
Why is my signature header mismatched?
Here are common reasons the signature header may be mismatched.
- Check if the header is set lower-cased. Some server frameworks (e.g. Next.js) use lowercase request header names since they are case-insensitive (RFC 2616).
- Make sure you're passing the entire body as the message in the HMAC signature. Some frameworks require you to configure the HTTP handler to not parse the request body (e.g. Next.js).
- Make sure your API key is valid.
What IP address will webhook requests come from?
Webhooks will be sent from the IP address 44.225.232.73
.
How often will webhook requests be retried?
Webhooks are retried every five minutes for up to one hour until a 2xx
response is returned.
Can I filter which webhook events are sent?
Currently there is no way to filter which events are sent to your webhook URLs. Paper may add new webhook event types without notice. Please ignore events that you don't need by returning a 2xx
response.