Skip to main content
POST
/
webhook
/
stripe
/
webhook
Stripe billing events webhook
curl --request POST \
  --url https://api.royalti.io/webhook/stripe/webhook \
  --header 'Content-Type: application/json' \
  --data '
{
  "id": "evt_1234567890abcdef",
  "type": "customer.subscription.updated",
  "data": {
    "object": {},
    "previous_attributes": {}
  },
  "api_version": "2024-06-20",
  "created": 1705939200
}
'
import requests

url = "https://api.royalti.io/webhook/stripe/webhook"

payload = {
"id": "evt_1234567890abcdef",
"type": "customer.subscription.updated",
"data": {
"object": {},
"previous_attributes": {}
},
"api_version": "2024-06-20",
"created": 1705939200
}
headers = {"Content-Type": "application/json"}

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

print(response.text)
const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
id: 'evt_1234567890abcdef',
type: 'customer.subscription.updated',
data: {object: {}, previous_attributes: {}},
api_version: '2024-06-20',
created: 1705939200
})
};

fetch('https://api.royalti.io/webhook/stripe/webhook', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));
<?php

$curl = curl_init();

curl_setopt_array($curl, [
CURLOPT_URL => "https://api.royalti.io/webhook/stripe/webhook",
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([
'id' => 'evt_1234567890abcdef',
'type' => 'customer.subscription.updated',
'data' => [
'object' => [

],
'previous_attributes' => [

]
],
'api_version' => '2024-06-20',
'created' => 1705939200
]),
CURLOPT_HTTPHEADER => [
"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.royalti.io/webhook/stripe/webhook"

payload := strings.NewReader("{\n \"id\": \"evt_1234567890abcdef\",\n \"type\": \"customer.subscription.updated\",\n \"data\": {\n \"object\": {},\n \"previous_attributes\": {}\n },\n \"api_version\": \"2024-06-20\",\n \"created\": 1705939200\n}")

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

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))

}
HttpResponse<String> response = Unirest.post("https://api.royalti.io/webhook/stripe/webhook")
.header("Content-Type", "application/json")
.body("{\n \"id\": \"evt_1234567890abcdef\",\n \"type\": \"customer.subscription.updated\",\n \"data\": {\n \"object\": {},\n \"previous_attributes\": {}\n },\n \"api_version\": \"2024-06-20\",\n \"created\": 1705939200\n}")
.asString();
require 'uri'
require 'net/http'

url = URI("https://api.royalti.io/webhook/stripe/webhook")

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

request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"id\": \"evt_1234567890abcdef\",\n \"type\": \"customer.subscription.updated\",\n \"data\": {\n \"object\": {},\n \"previous_attributes\": {}\n },\n \"api_version\": \"2024-06-20\",\n \"created\": 1705939200\n}"

response = http.request(request)
puts response.read_body
{
  "received": true,
  "eventId": "evt_1234567890abcdef"
}
{
"status": "error",
"message": "Webhook signature verification failed"
}
{
"status": "error",
"message": "<string>",
"code": "<string>",
"details": {}
}
This endpoint requires authentication. Include your Bearer token in the Authorization header.

Description

Authentication: Requires stripe-signature header with HMAC-SHA256 signature. Supported Event Types:
  • customer.subscription.created - New subscription created
  • customer.subscription.updated - Subscription modified
  • customer.subscription.deleted - Subscription cancelled
  • customer.subscription.pending_update_applied - Pending changes applied
  • customer.subscription.pending_update_expired - Pending changes expired
  • invoice.payment_succeeded - Payment successful
  • invoice.payment_failed - Payment failed
  • invoice.created - Invoice generated
  • invoice.finalized - Invoice finalized
  • invoice.sent - Invoice sent to customer
  • invoice.paid - Invoice paid
  • invoice.payment_action_required - Customer action needed
  • invoice.overdue - Invoice past due
  • checkout.session.completed - Checkout completed
  • checkout.session.async_payment_succeeded - Async payment succeeded
  • customer.created - Customer created
  • customer.updated - Customer updated
  • billing.meter.created - Usage meter created
  • billing.meter.updated - Usage meter updated
  • billing.meter.deactivated - Usage meter deactivated
  • billing.meter.reactivated - Usage meter reactivated
Workflow:
  1. Verifies webhook signature using Stripe SDK
  2. Routes event to appropriate handler based on event type
  3. Syncs subscription data to local TenantSubscription model
  4. Invalidates and updates subscription cache
  5. Triggers revenue recovery workflows for failed payments
  6. Logs comprehensive event details to GCP Cloud Logging

Code Examples

const response = await fetch('https://api.royalti.io/webhook/stripe/webhook', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "id": "evt_1234567890abcdef",
    "type": "customer.subscription.updated",
    "api_version": "2024-06-20",
    "created": 1705939200,
    "data": {
      "object": {},
      "previous_attributes": {}
    }
  })
});

const data = await response.json();
console.log(data);
import requests

response = requests.post(
  'https://api.royalti.io/webhook/stripe/webhook',
  headers={
    'Authorization': f'Bearer {token}'
  },
  json={"id":"evt_1234567890abcdef","type":"customer.subscription.updated","api_version":"2024-06-20","created":1705939200,"data":{"object":{},"previous_attributes":{}}}
)

data = response.json()
print(data)
curl -X POST https://api.royalti.io/webhook/stripe/webhook \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":"evt_1234567890abcdef","type":"customer.subscription.updated","api_version":"2024-06-20","created":1705939200,"data":{"object":{},"previous_attributes":{}}}'

Body

application/json

Stripe Event object (structure varies by event type)

id
string
required

Unique Stripe event ID

Example:

"evt_1234567890abcdef"

type
string
required

Event type identifier

Example:

"customer.subscription.updated"

data
object
required

Event data containing the affected resource

api_version
string

Stripe API version

Example:

"2024-06-20"

created
integer

Unix timestamp when event was created

Example:

1705939200

Response

Webhook processed successfully

received
boolean
Example:

true

eventId
string
Example:

"evt_1234567890abcdef"