Flash Documentation
  • Welcome to the Flash Documentation!
  • Getting Started
    • Connect Your Wallet
      • Coinos Wallet
  • Products
    • Checkouts
      • Payment Links
      • Donation Widget
      • Page Paywalls
      • Payment Buttons
      • Video Paywalls
    • Subscriptions
      • Getting Started
        • Create a Subscription Plan
        • Pre-Fill User Details
      • Authentication
      • Endpoints
      • Webhooks
    • Stores
      • Managing Products
      • Hosted Store
      • Point of Sale
  • Integrations
    • WooCommerce
    • Shopify
    • Wix
  • API DOCUMENTATION
    • Welcome
    • Authentication
    • Subscriptions
      • Subscription-specific Authentication
      • Endpoints
      • Webhooks
  • NEED MORE HELP?
    • Contact Us
Powered by GitBook
On this page
  • Getting Started
  • Step 1: Create/Edit Subscription & Plug in Your Webhook URL
  • Step 2: Configure Your Webhook Endpoint
  • Event Types
  • Webhook Payload Structure
  • Securing Webhooks with JWT
  • Handling Webhooks
  • Best Practices
  • FAQ
  • Conclusion
  • Webhook Usage - full example
Export as PDF
  1. Products
  2. Subscriptions

Webhooks

Webhook Integration Guide for Subscription Management

PreviousEndpointsNextStores

Last updated 8 months ago

This guide is intended for developers looking to integrate real-time subscription event notifications into their applications. With our webhook system, you’ll receive automated POST requests to your designated URL for events like new sign-ups, renewals, and payment failures.

Getting Started

A webhook allows our system to send real-time updates 📡 to your application. We’ll POST a JSON payload to your specified endpoint whenever a relevant subscription event occurs.

Step 1: Create/Edit Subscription & Plug in Your Webhook URL

You can set up a webhook URL in the Flash web app either while creating a new subscription or by editing an existing subscription:

  1. When creating a new subscription:

    1. Head over to New Subs > Create a Subscription Plan

    2. Make sure to click on the “Use Advanced Webhook Features” checkbox to make the Webhook Url field appear

    3. Input your Webhook URL in the form when creating the subscription

    4. After creating the Subscription, you will receive a url with the subscription and the subscription key. You can use this key to decode and verify the JWT token that comes with our event (see guide below).

  2. When you have already created a subscription:

    1. Head over to My Subscriptions

    2. Select the subscription you want to add webhooks for

    3. Click on “Edit Subscription”

    4. Fill in/modify the Webhook Url field

    5. The subscription key is also visible in this screen and can be used to decode and verify the JWT token that comes with our event (see guide below).

Step 2: Configure Your Webhook Endpoint

To receive webhook notifications, set up a POST endpoint in your application:

  1. Ensure it accepts JSON payloads.

  2. Process the data based on the event type and take appropriate actions.

Security Tip: Always use HTTPS to secure webhook transmissions.

Responding to Webhook Calls

Your webhook endpoint should respond with a 200 OK status to acknowledge receipt of the event. If no response or an error is received, we may retry the notification.


Event Types

The system triggers the following events, each sending a POST request with event data to your webhook:

1. User Signed Up (name='user_signed_up', id=1)

Triggered when a user subscribes to a plan. The payload includes details like the user's public key, plan, and transaction info.

{
  "data": {
    "public_key": "55a12716a6c4e8c95fc83dc046c3ea2209d3e3a1b87b15c48ef562b5a8599ed8",
    "external_uuid": "adf8cab9-add3-400d-b83c-9b68cfb6be8d",
    "name": "Jane Doe",
    "email": "jane.doe@example.com",
    "about": "Enthusiast of digital art",
    "picture_url": "<https://example.com/jane.jpg>",
    "user_plan": "Premium",
    "user_plan_id": "23",
    "signup_date": "2024-02-15",
    "next_payment_date": "2024-03-15",
    "failed_payment_date": "",
    "transaction_id": "654",
    "transaction_amount": "100",
    "transaction_currency": "Satoshis"
    "transaction_date": "2024-02-15"
  }
}
2. Renewal Successful (name='renewal_successful', id=2)

Sent when a user's subscription renewal is successfully processed.

{
  "data": {
    "public_key": "55a12716a6c4e8c95fc83dc046c3ea2209d3e3a1b87b15c48ef562b5a8599ed8",
    "external_uuid": "adf8cab9-add3-400d-b83c-9b68cfb6be8d",
    "name": "Jane Doe",
    "email": "jane.doe@example.com",
    "about": "Enthusiast of digital art",
    "picture_url": "<https://example.com/jane.jpg>",
    "user_plan": "Premium",
    "user_plan_id": "24"
    "signup_date": "2024-02-15",
    "next_payment_date": "2024-04-15",
    "failed_payment_date": "",
    "transaction_id": "837",
    "transaction_amount": "100",
    "transaction_currency": "Satoshis",
    "transaction_date": "2024-03-15"
  }
}
3. Renewal Failed (name='renewal_failed', id=3)

Sent if a subscription renewal fails, typically due to a payment issue.

{
  "data": {
    "public_key": "55a12716a6c4e8c95fc83dc046c3ea2209d3e3a1b87b15c48ef562b5a8599ed8",
    "external_uuid": "adf8cab9-add3-400d-b83c-9b68cfb6be8d",
    "name": "Jane Doe",
    "email": "jane.doe@example.com",
    "about": "Enthusiast of digital art",
    "picture_url": "<https://example.com/jane.jpg>",
    "user_plan": "Premium",
    "user_plan_id": "67",
    "signup_date": "2024-02-15",
    "next_payment_date": "",
    "failed_payment_date": "2024-03-15",
    "transaction_id": "-1",
    "transaction_amount": "100",
    "transaction_currency": "Satoshis",
    "transaction_date": ""
  }
}
4. Subscription Paused (name='user_paused_subscription', id=4)

Sent if a subscription renewal fails, typically due to a payment issue.

{
  "data": {
    "public_key": "55a12716a6c4e8c95fc83dc046c3ea2209d3e3a1b87b15c48ef562b5a8599ed8",
    "external_uuid": "adf8cab9-add3-400d-b83c-9b68cfb6be8d",
    "name": "Jane Doe",
    "email": "jane.doe@example.com",
    "about": "Enthusiast of digital art",
    "picture_url": "<https://example.com/jane.jpg>",
    "user_plan": "Premium",
    "user_plan_id": "67",
    "signup_date": "2024-02-15",
    "next_payment_date": "",
    "failed_payment_date": "2024-03-15",
    "transaction_id": "-1",
    "transaction_amount": "100",
    "transaction_currency": "Satoshis",
    "transaction_date": ""
  }
}
5. Subscription Cancelled (name='user_cancelled_subscription', id=5)

Sent if a subscription renewal fails, typically due to a payment issue.

{
  "data": {
    "public_key": "55a12716a6c4e8c95fc83dc046c3ea2209d3e3a1b87b15c48ef562b5a8599ed8",
    "external_uuid": "adf8cab9-add3-400d-b83c-9b68cfb6be8d",
    "name": "Jane Doe",
    "email": "jane.doe@example.com",
    "about": "Enthusiast of digital art",
    "picture_url": "<https://example.com/jane.jpg>",
    "user_plan": "Premium",
    "user_plan_id": "67",
    "signup_date": "2024-02-15",
    "next_payment_date": "",
    "failed_payment_date": "2024-03-15",
    "transaction_id": "-1",
    "transaction_amount": "100",
    "transaction_currency": "Satoshis",
    "transaction_date": ""
  }
}

Webhook Payload Structure

The data sent to your webhook URL will be structured as a JSON object containing two primary fields: eventType and data. The eventType field indicates the type of event, while the data field contains event-specific information.

Here's a breakdown of the data you can expect for each event type:
  • Common Fields Across All Event Types:

    • public_key: The user's public key.

    • name: The user's name.

    • email: The user's email address.

    • about: A brief description of the user.

    • picture_url: The URL to the user's profile picture.

    • user_plan: The subscription plan the user has signed up for.

    • user_plan_id: The subscription plan id the user has signed up for.

  • Event-Specific Fields:

    • signup_date: The date the user signed up (only for user_signed_up).

    • next_payment_date: The next payment date for the subscription (only for renewal_successful).

    • failed_payment_date: The date of the failed payment (only for renewal_failed).

    • transaction_id: The ID of the transaction (varies by event type).

    • transaction_amount: The payment amount of the transaction(all same)

    • transaction_currency: The currency of the transaction(all same)

    • transaction_date: The date of the transaction (varies by event type).


Securing Webhooks with JWT

All webhooks include a JWT token in the Authorization header, allowing you to verify that the event is coming from Flash. You can decode and verify this token using the subscription key provided in the web app.

Example token:

{
  "version": "1.0",
  "eventType": {
    "id": "1",
    "name": "user_signed_up"
  },
  "user_public_key": "55a12716a6c4e8c95fc83dc046c3ea2209d3e3a1b87b15c48ef562b5a8599ed8",
  "exp": "2024-05-01T08:48:38Z"
}

Token Expiration: The token is valid for 1 hour.

To verify the token, decode it using your subscription key and the HS256 algorithm.


Handling Webhooks

Your webhook should always respond with a 200 OK status after processing the event. Failure to do so may cause the system to retry the notification.


Best Practices

  • Idempotency: Ensure that your webhook handler can process the same event more than once without causing errors or duplicate actions.

  • Logging: Implement logging for all webhook events to make troubleshooting easier.


FAQ

Can I set up multiple webhook URLs?

Can I set up multiple webhook URLs?

Currently, we support one webhook URL per subscription. You can handle different events with routing logic in your app.

What if I stop receiving webhooks?

Ensure your endpoint is correctly configured and reachable. If the issue persists, contact our support team.


Conclusion

By integrating our webhook system, you can stay updated on key subscription events in real time, helping automate and optimize your processes. Feel free to contact our developer support team if you need any help.


Webhook Usage - full example

Assuming your webhook URL is https://webhook , you can refer to the following code:

const jwt = require('jsonwebtoken');

app.post('/webhook', (req, res) => {
	const data = req.body;  // Extract JSON data from the request
	
	const authHeader = req.headers.authorization;
	const token = authHeader && authHeader.split(' ')[1];
	
	if (!token) {
	    return res.status(401).json({ error: 'No token provided' });
	}

	// Verify and decode the JWT token
	const secret = 'your_secret_key';
	jwt.verify(token, secret, (err, decodedToken) => {
	    if (err) {
	        return res.status(401).json({ error: 'Invalid token' });
	    }
	
	    const { version, eventType, user_public_key } = decodedToken;
	
	    // Process the data as needed
	    console.log('User public key:', user_public_key);
	    console.log('Event Type:', eventType);
	});
	
  // Access fields from the JSON payload
  const userPublicKey = data.data.public_key;
  const userName = data.data.name;
  const userEmail = data.data.email;
  
  // Process the data...
	
  res.json({ ok: true });
});
from flask import request, jsonify
import jwt

@app.route('/webhook', methods=['POST'])
def webhook():
    data = request.get_json()

    auth_header = request.headers.get('Authorization')
    token = auth_header.split(" ")[1] if auth_header else None

    if not token:
        return jsonify({'error': 'No token provided'}), 401

    secret = 'your_secret_key'
    try:
        decoded_token = jwt.decode(token, secret, algorithms=['HS256'])
        version = decoded_token.get('version')
        event_type = decoded_token.get('eventType')
        user_public_key = decoded_token.get('user_public_key')

        print(f"User public key: {user_public_key}")
        print(f"Event Type: {event_type}")

    except jwt.ExpiredSignatureError:
        return jsonify({'error': 'Token has expired'}), 401
    except jwt.InvalidTokenError:
        return jsonify({'error': 'Invalid token'}), 401

    user_public_key = data.get('data').get('public_key')
    user_name = data.get('data').get('name')
    user_email = data.get('data').get('email')
    
    return jsonify({'ok': True})
require 'json'
require 'jwt'

post '/webhook' do
  data = JSON.parse(request.body.read)

  auth_header = request.env['HTTP_AUTHORIZATION']
  token = auth_header.split(' ')[1] if auth_header

  halt 401, { error: 'No token provided' }.to_json unless token

  secret = 'your_secret_key'
  begin
    decoded_token = JWT.decode(token, secret, true, algorithm: 'HS256')[0]
    user_public_key = decoded_token['user_public_key']
    event_type = decoded_token['eventType']

    puts "User public key: #{user_public_key}"
    puts "Event Type: #{event_type}"
  
  rescue JWT::ExpiredSignature
    halt 401, { error: 'Token has expired' }.to_json
  rescue JWT::DecodeError
    halt 401, { error: 'Invalid token' }.to_json
  end

  user_public_key = data['data']['public_key']
  user_name = data['data']['name']
  user_email = data['data']['email']
  
  { ok: true }.to_json
end
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import io.jsonwebtoken.*;
import java.util.Map;

@RestController
public class WebhookController {

    @PostMapping("/webhook")
    public ResponseEntity<?> handleWebhook(@RequestBody Map<String, Object> payload, 
                                           @RequestHeader("Authorization") String authHeader) {
        
        String token = authHeader != null ? authHeader.split(" ")[1] : null;
        if (token == null) {
            return ResponseEntity.status(401).body(Map.of("error", "No token provided"));
        }

        String secret = "your_secret_key";
        try {
            Jws<Claims> decodedToken = Jwts.parser()
                                            .setSigningKey(secret)
                                            .parseClaimsJws(token);
            
            String version = (String) decodedToken.getBody().get("version");
            Map<String, Object> eventType = (Map<String, Object>) decodedToken.getBody().get("eventType");
            String userPublicKey = (String) decodedToken.getBody().get("user_public_key");

            System.out.println("User public key: " + userPublicKey);
            System.out.println("Event Type: " + eventType);

        } catch (ExpiredJwtException e) {
            return ResponseEntity.status(401).body(Map.of("error", "Token has expired"));
        } catch (JwtException e) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid token"));
        }

        Map<String, Object> data = (Map<String, Object>) payload.get("data");
        String userPublicKey = (String) data.get("public_key");
        String userName = (String) data.get("name");
        String userEmail = (String) data.get("email");

        return ResponseEntity.ok(Map.of("ok", true));
    }
}
require 'vendor/autoload.php'; // Include JWT library (e.g., firebase/php-jwt)

use \Firebase\JWT\JWT;

$secret = 'your_secret_key';

$data = json_decode(file_get_contents('php://input'), true);

$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? null;
if ($authHeader) {
    $token = explode(' ', $authHeader)[1];
} else {
    http_response_code(401);
    echo json_encode(['error' => 'No token provided']);
    exit;
}

try {
    $decodedToken = JWT::decode($token, $secret, ['HS256']);
    $decodedArray = (array) $decodedToken;

    $userPublicKey = $decodedArray['user_public_key'];
    $eventType = $decodedArray['eventType'];

    echo "User public key: $userPublicKey\n";
    echo "Event Type: $eventType\n";

} catch (Exception $e) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid token']);
    exit;
}

$userPublicKey = $data['data']['public_key'] ?? null;
$userName = $data['data']['name'] ?? null;
$userEmail = $data['data']['email'] ?? null;

echo json_encode(['ok' => true]);
Page cover image