Created
October 21, 2025 22:55
-
-
Save thecompez/231aff5cc69571757041f9f898c2b4a8 to your computer and use it in GitHub Desktop.
WebHook
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| // Headers - SET THESE FIRST | |
| header('Content-Type: application/json'); | |
| header('Access-Control-Allow-Origin: *'); | |
| header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); | |
| header('Access-Control-Allow-Headers: Content-Type'); | |
| // Handle preflight requests | |
| if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { | |
| exit(0); | |
| } | |
| // Turn off HTML errors | |
| ini_set('display_errors', '0'); | |
| ini_set('log_errors', '1'); | |
| error_reporting(E_ALL); | |
| // Simple response function | |
| function jsonResponse($success, $message, $data = []) { | |
| $response = ['success' => $success]; | |
| if ($success) { | |
| $response['message'] = $message; | |
| if (!empty($data)) { | |
| $response['data'] = $data; | |
| } | |
| } else { | |
| $response['error'] = $message; | |
| } | |
| echo json_encode($response); | |
| exit; | |
| } | |
| // Only allow POST | |
| if ($_SERVER['REQUEST_METHOD'] !== 'POST') { | |
| http_response_code(405); | |
| jsonResponse(false, 'Only POST requests allowed'); | |
| } | |
| class FarcasterWebhookHandler { | |
| private array $webhookData = []; | |
| private string $logFile = 'logs/farcaster_webhook.log'; | |
| // Supported webhook events from Base docs | |
| private const SUPPORTED_EVENTS = [ | |
| 'miniapp_added', | |
| 'frame_added', | |
| 'notifications_enabled', | |
| 'miniapp_removed', | |
| 'frame_removed', | |
| 'notifications_disabled' | |
| ]; | |
| public function __construct() { | |
| $this->readAndValidateInput(); | |
| } | |
| /** | |
| * Read and validate input data | |
| */ | |
| private function readAndValidateInput(): void { | |
| $rawInput = file_get_contents('php://input'); | |
| if (empty($rawInput)) { | |
| jsonResponse(false, 'Empty request body'); | |
| } | |
| $this->log("Raw webhook data received: " . $rawInput); | |
| $decoded = json_decode($rawInput, true); | |
| if (json_last_error() !== JSON_ERROR_NONE) { | |
| $this->log("JSON decode error: " . json_last_error_msg()); | |
| jsonResponse(false, 'Invalid JSON input: ' . json_last_error_msg()); | |
| } | |
| if (!is_array($decoded)) { | |
| jsonResponse(false, 'Invalid webhook data structure'); | |
| } | |
| $this->webhookData = $decoded; | |
| } | |
| /** | |
| * Parse webhook event according to Base/Farcaster format | |
| */ | |
| private function parseWebhookEvent(): array { | |
| $requiredFields = ['fid', 'appFid', 'event']; | |
| foreach ($requiredFields as $field) { | |
| if (!isset($this->webhookData[$field])) { | |
| throw new Exception("Missing required field: {$field}"); | |
| } | |
| } | |
| $fid = $this->webhookData['fid']; | |
| $appFid = $this->webhookData['appFid']; | |
| $eventData = $this->webhookData['event']; | |
| if (!isset($eventData['event'])) { | |
| throw new Exception("Missing event type in event data"); | |
| } | |
| $this->log("Webhook received - FID: {$fid}, App FID: {$appFid}, Event: {$eventData['event']}"); | |
| return [ | |
| 'fid' => $fid, | |
| 'appFid' => $appFid, | |
| 'event' => $eventData['event'], | |
| 'notificationDetails' => $eventData['notificationDetails'] ?? null | |
| ]; | |
| } | |
| /** | |
| * Store notification token in database using your existing method | |
| */ | |
| private function storeTokenInDatabase(int $fid, int $appFid, array $notificationDetails): bool { | |
| try { | |
| // Include database using your existing method | |
| require_once '../../lib/db.php'; | |
| // Get database connection | |
| $conn = getDatabaseConnection('geny'); | |
| if (!$conn || $conn->connect_error) { | |
| $error = $conn ? $conn->connect_error : 'Connection failed'; | |
| $this->log("Database connection failed: " . $error); | |
| return false; | |
| } | |
| // Escape inputs for safety | |
| $escapedUrl = $conn->real_escape_string($notificationDetails['url']); | |
| $escapedToken = $conn->real_escape_string($notificationDetails['token']); | |
| // Check if record exists | |
| $checkSql = "SELECT id FROM user_notification_tokens WHERE fid = $fid AND app_fid = $appFid"; | |
| $result = $conn->query($checkSql); | |
| if ($result && $result->num_rows > 0) { | |
| // Update existing record | |
| $sql = "UPDATE user_notification_tokens | |
| SET notification_url = '$escapedUrl', | |
| notification_token = '$escapedToken', | |
| updated_at = NOW() | |
| WHERE fid = $fid AND app_fid = $appFid"; | |
| } else { | |
| // Insert new record | |
| $sql = "INSERT INTO user_notification_tokens | |
| (fid, app_fid, notification_url, notification_token, created_at, updated_at) | |
| VALUES ($fid, $appFid, '$escapedUrl', '$escapedToken', NOW(), NOW())"; | |
| } | |
| $success = $conn->query($sql); | |
| if (!$success) { | |
| $this->log("Database error: " . $conn->error); | |
| } | |
| $conn->close(); | |
| return $success; | |
| } catch (Exception $e) { | |
| $this->log("Database error storing token: " . $e->getMessage()); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Remove notification token from database | |
| */ | |
| private function removeTokenFromDatabase(int $fid, int $appFid): bool { | |
| try { | |
| require_once '../../lib/db.php'; | |
| $conn = getDatabaseConnection('geny'); | |
| if (!$conn || $conn->connect_error) { | |
| $error = $conn ? $conn->connect_error : 'Connection failed'; | |
| $this->log("Database connection failed: " . $error); | |
| return false; | |
| } | |
| $sql = "DELETE FROM user_notification_tokens WHERE fid = $fid AND app_fid = $appFid"; | |
| $success = $conn->query($sql); | |
| if (!$success) { | |
| $this->log("Database error: " . $conn->error); | |
| } | |
| $conn->close(); | |
| return $success; | |
| } catch (Exception $e) { | |
| $this->log("Database error removing token: " . $e->getMessage()); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Handles token storage from webhook events | |
| */ | |
| private function handleTokenStorage(array $parsedData): void { | |
| $fid = $parsedData['fid']; | |
| $appFid = $parsedData['appFid']; | |
| $eventType = $parsedData['event']; | |
| $tokenEvents = [ | |
| 'frame_added' => 'store', | |
| 'notifications_enabled' => 'store', | |
| 'miniapp_removed' => 'clear', | |
| 'notifications_disabled' => 'clear' | |
| ]; | |
| if (!isset($tokenEvents[$eventType])) { | |
| $this->log("Event {$eventType} does not require token handling"); | |
| return; | |
| } | |
| if ($tokenEvents[$eventType] === 'store') { | |
| if (!$parsedData['notificationDetails']) { | |
| $this->log("{$eventType} event received but no notificationDetails provided"); | |
| return; | |
| } | |
| $details = $parsedData['notificationDetails']; | |
| if (!isset($details['url'], $details['token'])) { | |
| $this->log("{$eventType} event received but incomplete notificationDetails"); | |
| return; | |
| } | |
| // Store in database | |
| if ($this->storeTokenInDatabase($fid, $appFid, $details)) { | |
| $this->log("Stored token from {$eventType} event for FID: {$fid}, App FID: {$appFid}"); | |
| // Send welcome notification for frame_added | |
| if ($eventType === 'frame_added') { | |
| $this->sendWelcomeNotification($fid, $appFid, $details); | |
| } | |
| } else { | |
| $this->log("Failed to store token in database for FID: {$fid}"); | |
| } | |
| } else { | |
| if ($this->removeTokenFromDatabase($fid, $appFid)) { | |
| $this->log("Cleared token due to {$eventType} event for FID: {$fid}, App FID: {$appFid}"); | |
| } else { | |
| $this->log("Failed to clear token from database for FID: {$fid}"); | |
| } | |
| } | |
| } | |
| /** | |
| * Send welcome notification when mini app is added | |
| */ | |
| private function sendWelcomeNotification(int $fid, int $appFid, array $notificationDetails): void { | |
| $notificationData = [ | |
| 'notificationId' => 'welcome-' . $fid . '-' . time(), | |
| 'title' => 'Welcome to GenyTask! 🎉', | |
| 'body' => 'Thank you for adding our mini app! You will receive notifications for new tasks and updates.', | |
| 'targetUrl' => 'https://task.genyapps.xyz', | |
| 'tokens' => [$notificationDetails['token']] | |
| ]; | |
| $this->sendNotificationRequest($notificationDetails['url'], $notificationData); | |
| } | |
| /** | |
| * Send notification to Farcaster API | |
| */ | |
| private function sendNotificationRequest(string $url, array $notificationData): void { | |
| $ch = curl_init($url); | |
| curl_setopt_array($ch, [ | |
| CURLOPT_POST => true, | |
| CURLOPT_RETURNTRANSFER => true, | |
| CURLOPT_HTTPHEADER => ['Content-Type: application/json'], | |
| CURLOPT_POSTFIELDS => json_encode($notificationData), | |
| CURLOPT_TIMEOUT => 10 | |
| ]); | |
| $response = curl_exec($ch); | |
| $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
| curl_close($ch); | |
| $this->log("Welcome notification sent - HTTP Code: {$httpCode}, Response: {$response}"); | |
| } | |
| /** | |
| * Processes the webhook event | |
| */ | |
| public function processWebhook(): void { | |
| try { | |
| $parsedData = $this->parseWebhookEvent(); | |
| $event = $parsedData['event']; | |
| // Validate event type | |
| if (!in_array($event, self::SUPPORTED_EVENTS)) { | |
| $this->log("Unhandled webhook event: {$event}"); | |
| jsonResponse(false, "Unsupported event type: {$event}"); | |
| } | |
| $this->log("Processing webhook event: {$event}"); | |
| $this->handleTokenStorage($parsedData); | |
| jsonResponse(true, "Webhook processed successfully"); | |
| } catch (Exception $e) { | |
| $this->log("Error processing webhook: " . $e->getMessage()); | |
| jsonResponse(false, $e->getMessage()); | |
| } | |
| } | |
| /** | |
| * Log messages with timestamp | |
| */ | |
| private function log(string $message): void { | |
| $timestamp = date('Y-m-d H:i:s'); | |
| error_log("[{$timestamp}] {$message}"); | |
| } | |
| } | |
| // Execute the webhook handler | |
| try { | |
| $handler = new FarcasterWebhookHandler(); | |
| $handler->processWebhook(); | |
| } catch (Throwable $e) { | |
| jsonResponse(false, 'Internal server error: ' . $e->getMessage()); | |
| } | |
| ?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment