Created
October 21, 2025 19:31
-
-
Save thecompez/6d9f147ca2f6f046a412e941a1f15ba4 to your computer and use it in GitHub Desktop.
webhook php
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); | |
| require '../../lib/vendor/autoload.php'; | |
| require "../../lib/core.php"; | |
| header('Content-Type: application/json'); | |
| class FarcasterWebhookHandler { | |
| private array $webhookData = []; | |
| private string $rawInput; | |
| private string $logFile = 'logs/farcaster_webhook.log'; | |
| // Supported webhook events from Base docs | |
| private const SUPPORTED_EVENTS = [ | |
| 'miniapp_added', | |
| 'notifications_enabled', | |
| 'miniapp_removed', | |
| 'notifications_disabled' | |
| ]; | |
| public function __construct() { | |
| $this->setupEnvironment(); | |
| $this->validateRequest(); | |
| $this->readAndValidateInput(); | |
| } | |
| /** | |
| * Set up the runtime environment | |
| */ | |
| private function setupEnvironment(): void { | |
| // Error logging | |
| ini_set('log_errors', '1'); | |
| ini_set('error_log', $this->logFile); | |
| // Security headers | |
| header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); | |
| header('Pragma: no-cache'); | |
| header('Expires: 0'); | |
| header('Content-Type: application/json'); | |
| // Start session for token persistence | |
| if (session_status() === PHP_SESSION_NONE) { | |
| session_start(); | |
| } | |
| } | |
| /** | |
| * Validate the request method and content type | |
| */ | |
| private function validateRequest(): void { | |
| if ($_SERVER['REQUEST_METHOD'] !== 'POST') { | |
| $this->fail('Method not allowed', 405); | |
| } | |
| $contentType = $_SERVER['CONTENT_TYPE'] ?? ''; | |
| if (stripos($contentType, 'application/json') === false) { | |
| $this->fail('Unsupported Media Type: Expected application/json', 415); | |
| } | |
| } | |
| /** | |
| * Read and validate input data | |
| */ | |
| private function readAndValidateInput(): void { | |
| $this->rawInput = file_get_contents("php://input"); | |
| if (empty($this->rawInput)) { | |
| $this->fail('Empty request body', 400); | |
| } | |
| $this->log("Raw webhook data received: " . $this->rawInput); | |
| $decoded = json_decode($this->rawInput, true); | |
| if (json_last_error() !== JSON_ERROR_NONE) { | |
| $this->log("JSON decode error: " . json_last_error_msg()); | |
| $this->fail('Invalid JSON input: ' . json_last_error_msg(), 400); | |
| } | |
| if (!is_array($decoded)) { | |
| $this->fail('Invalid webhook data structure', 400); | |
| } | |
| $this->webhookData = $decoded; | |
| } | |
| /** | |
| * Parse webhook event according to Base/Farcaster format | |
| */ | |
| private function parseWebhookEvent(): array { | |
| // According to Base docs, the webhook data contains: | |
| // - fid: The user's FID | |
| // - appFid: The client's FID (Base app is 309857) | |
| // - event: The event payload with type and notification details | |
| $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 | |
| ]; | |
| } | |
| /** | |
| * Handles token storage from webhook events | |
| */ | |
| private function handleTokenStorage(array $parsedData): void { | |
| $fid = $parsedData['fid']; | |
| $appFid = $parsedData['appFid']; | |
| $eventType = $parsedData['event']; | |
| $tokenEvents = [ | |
| 'miniapp_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; | |
| } | |
| $userKey = "notification_token_{$fid}_{$appFid}"; | |
| 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 session (you might want to use a database instead for production) | |
| $_SESSION[$userKey] = [ | |
| "url" => $details['url'], | |
| "token" => $details['token'], | |
| "fid" => $fid, | |
| "appFid" => $appFid | |
| ]; | |
| $this->log("Stored token from {$eventType} event for FID: {$fid}, App FID: {$appFid}"); | |
| // Send welcome notification for miniapp_added | |
| if ($eventType === 'miniapp_added') { | |
| $this->sendWelcomeNotification($fid, $appFid, $details); | |
| } | |
| } else { | |
| unset($_SESSION[$userKey]); | |
| $this->log("Cleared token due to {$eventType} event for FID: {$fid}, App FID: {$appFid}"); | |
| } | |
| } | |
| /** | |
| * Send welcome notification when mini app is added | |
| */ | |
| private function sendWelcomeNotification(int $fid, int $appFid, array $notificationDetails): void { | |
| // You'll need to implement this based on the sendNotification example | |
| $this->log("Would send welcome notification to FID: {$fid}, App FID: {$appFid}"); | |
| // Example implementation: | |
| /* | |
| $notificationData = [ | |
| 'notificationId' => uniqid(), | |
| 'title' => 'Welcome to Our App', | |
| 'body' => 'Thank you for adding our mini app!', | |
| 'targetUrl' => 'https://yourdomain.com', // Must be your domain | |
| 'tokens' => [$notificationDetails['token']] | |
| ]; | |
| // Make POST request to notification URL | |
| $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) | |
| ]); | |
| $response = curl_exec($ch); | |
| $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
| curl_close($ch); | |
| $this->log("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}"); | |
| $this->fail("Unsupported event type: {$event}", 400); | |
| } | |
| $this->log("Processing webhook event: {$event}"); | |
| $this->handleTokenStorage($parsedData); | |
| $this->success(); | |
| } catch (Exception $e) { | |
| $this->log("Error processing webhook: " . $e->getMessage()); | |
| $this->fail($e->getMessage(), 400); | |
| } | |
| } | |
| /** | |
| * Send success response | |
| */ | |
| private function success(): void { | |
| http_response_code(200); | |
| echo json_encode([ | |
| "status" => "success", | |
| "timestamp" => time() | |
| ]); | |
| exit; | |
| } | |
| /** | |
| * Send error response and exit | |
| */ | |
| private function fail(string $message, int $statusCode = 400): void { | |
| http_response_code($statusCode); | |
| echo json_encode([ | |
| "error" => $message, | |
| "timestamp" => time() | |
| ]); | |
| exit; | |
| } | |
| /** | |
| * 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) { | |
| // Global fallback error handler | |
| http_response_code(500); | |
| header('Content-Type: application/json'); | |
| echo json_encode([ | |
| "error" => "Internal server error", | |
| "timestamp" => time() | |
| ]); | |
| exit; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment