Webhooks

Learn how to use webhooks to receive real-time updates on important events related to your TGmembership-powered Telegram bot.

TGmembership offers support for webhooks, allowing you to receive real-time updates on crucial events related to your Telegram bot. Webhooks provide push API calls that can notify your application when specific events occur, such as new payments, renewals, cancellations, donations, and other relevant data. By configuring your webhook URL, you can start receiving real-time updates in JSON format and stay informed about critical events.

Setting Up a Webhook

To learn how to generate add a webhook endpoint, please refer to our guide on Setting Up a Webhook.

Payload Schema

The webhook payload for TGmembership has a simple schema with three elements: "event", "debug_id", and "data". The "event" element specifies the type of event that has occurred, while "debug_id" provides an identifier for debugging purposes. The "data" element contains element-specific data related to the event. Each event has its own data structure, which will be detailed separately in the documentation.

KeyDescription

event

A string representing the type of event that triggered the webhook, such as "order_completed" or "membership_terminated".

debug_id

A string identifier for the webhook event, which is not guaranteed to be unique and is used solely for debugging purposes. If you have any questions or issues related to an event, please provide this ID when contacting TGmembership support team.

data

An object containing data related to the event that triggered the webhook. The structure and contents of this object will vary depending on the type of event.

Verifying the Signature

To generate the signature for the webhook request, several steps are taken. First, a string is created by concatenating the nonce (provided in the header), timestamp, and the payload data using a period as a delimiter. The resulting string is then passed through a hashing algorithm called sha512 along with a secret key to generate a unique hash. The hash is then transformed to uppercase characters. Finally, the timestamp and the resulting hash are combined into a signature string using the format t=<timestamp>,v1=<hash>. This signature string is then included as a header with the webhook request to authenticate and validate the request.

Python
import re
import hashlib
import hmac

nonce = '53ed4554ef588'
signature = 't=1684096282,v1=F7866D2B2560641C5E33A60485B53CB0848C94BB4B1D727BB60678DDA4000A556E4AAC49354F10E0EFA8708A73BD30E49F8AC1C7451661E11255622131127413'
payload = '{"event":"membership_terminated","debug_id":"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN","data":{"member_id":1111111111}}'
secretKey = 'your_secret_key'

def verifySignature(nonce, signature, payload, secretKey):
    match = re.match(r'^t=(\d+),v1=[A-Z0-9]+$', signature)
    if not match:
        return False
    timestamp = match.group(1)
    expectedSignature = generateSignature(nonce, timestamp, payload, secretKey)
    return hmac.compare_digest(expectedSignature, signature)

def generateSignature(nonce, timestamp, post, secretKey):
    signature_string = f"{nonce}.{timestamp}.{payload}"
    signature = hmac.new(secretKey.encode(), signature_string.encode(), hashlib.sha512).hexdigest().upper()
    return f"t={timestamp},v1={signature}"

print(verifySignature(nonce, signature, payload, secretKey))
Node.js
const crypto = require('crypto');

const nonce = '53ed4554ef588';
const signature = 't=1684096282,v1=F7866D2B2560641C5E33A60485B53CB0848C94BB4B1D727BB60678DDA4000A556E4AAC49354F10E0EFA8708A73BD30E49F8AC1C7451661E11255622131127413';
const payload = '{"event":"membership_terminated","debug_id":"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN","data":{"member_id":1111111111}}';
const secretKey = 'your_secret_key';

function verifySignature(nonce, signature, payload, secretKey) {
    const signatureParts = signature.match(/^t=(\d+),v1=[A-Z0-9]+$/);
    if (!signatureParts)
        return false;
    const timestamp = signatureParts[1];
    return generateSignature(nonce, timestamp, payload, secretKey) === signature;
}

function generateSignature(nonce, timestamp, payload, secretKey) {
    const signatureString = nonce + '.' + timestamp + '.' + payload;
    const signature = crypto
        .createHmac('sha512', secretKey)
        .update(signatureString)
        .digest('hex')
        .toUpperCase();
    return `t=${timestamp},v1=${signature}`;
}

console.log(verifySignature(nonce, signature, payload, secretKey));
PHP
<?php
$nonce = '53ed4554ef588';
$signature = 't=1684096282,v1=F7866D2B2560641C5E33A60485B53CB0848C94BB4B1D727BB60678DDA4000A556E4AAC49354F10E0EFA8708A73BD30E49F8AC1C7451661E11255622131127413';
$payload = '{"event":"membership_terminated","debug_id":"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN","data":{"member_id":1111111111}}';
$secretKey = 'your_secret_key';

function verifySignature($nonce, $signature, $payload, $secretKey) {
    if (!preg_match('/^t=(\d+),v1=[A-Z0-9]+$/', $signature, $signatureParts))
        return false;
    $timestamp = $signatureParts[1];
    return hash_equals(generateSignature($nonce, $timestamp, $payload, $secretKey), $signature);
}

function generateSignature($nonce, $timestamp, $payload, $secretKey) {
    $signature = $nonce . '.' . $timestamp . '.' . $payload;
    $signature = strtoupper(hash_hmac('sha512', $signature, $secretKey));
    $signature = sprintf('t=%d,v1=%s', $timestamp, $signature);
    return $signature;
}

var_dump(verifySignature($nonce, $signature, $payload, $secretKey));

Webhook Delivery

TGmembership sends webhook updates to your endpoint as soon as they happen. Once an update is sent, TGmembership expects a response with http code 200 to confirm successful delivery.

If the first delivery attempt fails, TGmembership will automatically try to redeliver the update up to seven times, using the following schedule:

  • 2nd attempt: approximately 2 minutes after the first attempt

  • 3rd attempt: approximately 20 minutes after the second attempt

  • 4th attempt: approximately 6 hours after the third attempt

  • 5th attempt: approximately 14 hours after the fourth attempt

  • 6th attempt: approximately 30 hours after the fifth attempt

  • 7th attempt: approximately 2 days after the sixth attempt

If after the seventh attempt, the update still cannot be delivered, it will be considered unsuccessful, and TGmembership may delete the webhook endpoint to prevent further delivery attempts.

Request Headers

When a webhook event is sent to your server, it will include three custom headers to help you verify the authenticity of the request and understand its delivery status.

HeaderDescription

TGMEMBERSHIP-NONCE

This header contains a random string that is generated for each request.

TGMEMBERSHIP-SIGNATURE

This header contains a cryptographic signature that is generated based on the request payload and a secret key shared between TGmembership and your server. You can use this signature to verify that the request came from TGmembership and has not been modified in transit.

TGMEMBERSHIP-DELIVERY-ATTEMPT

This header contains a number that indicates how many times TGmembership has attempted to deliver this event to your server. In case of delivery failures, TGmembership will try up to 7 times to deliver the event before giving up. The header value will be incremented for each attempt.

Event: order_completed

This event is triggered when an order has been successfully completed and payment has been received.

KeyDescription

amount

A string representing the amount of payment made.

currency

A string representing the currency code for the payment.

is_donation

A boolean indicating whether the payment was a donation or not.

is_renewal

A boolean indicating whether the payment was a renewal or not.

member_id

An integer representing the Telegram user ID of the member.

membership_end_date

An integer representing the date and time (in Unix timestamp format) of the expected end date for the membership.

order_date

An integer representing the date and time (in Unix timestamp format) when the payment was made.

order_key

A string representing a unique identifier for the payment order.

payment_method

A lower-cased string representing the payment method used (e.g., stripe, paypal, etc.).

plan_id

An integer representing the plan ID associated with the payment.

project_id

An integer representing the project ID associated with the payment.

referred_by

An integer representing the Telegram user ID of the member who referred the paying member.

subscription_id

A string representing a unique identifier for the subscription.

{
    "event": "order_completed",
    "debug_id": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN",
    "data": {
        "amount": "10",
        "currency": "EUR",
        "is_donation": false,
        "is_renewal": true,
        "member_id": 1111111111,
        "order_date": 1684080114,
        "order_key": "abcdefghijklmnopqrstuvwxyz",
        "payment_method": "stripe",
        "plan_id": 1,
        "project_id": 1,
        "referred_by": 2222222222,
        "subscription_id": "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    }
}

Note: The elements currency, order_key, plan_id, project_id, is_renewal, referred_by, and subscription_id may not be included in the webhook payload if they are not relevant or their value is null.

Event: membership_terminated

This event is triggered when a membership has been terminated.

KeyDescription

manually_terminated

A boolean indicating whether the membership was manually terminated by an admin or not.

member_id

An integer representing the Telegram user ID of the member.

termination_date

Unix timestamp representing the date and time when the termination occurred.

plan_id

An integer representing the plan the member was subscribed to before termination.

project_id

An integer representing the ID of the project the user was associated with.

{
    "event": "membership_terminated",
    "debug_id": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN",
    "data": {
        "manually_terminated": true,
        "member_id": 1111111111,
        "termination_date": 1702276627,
        "plan_id": 1,
        "project_id": 1
    }
}

Last updated