Class: AssemblyaiCallbackTokenService
- Inherits:
-
Object
- Object
- AssemblyaiCallbackTokenService
- Defined in:
- app/services/assemblyai_callback_token_service.rb
Overview
Generates and validates time-limited JWT tokens for AssemblyAI webhook authentication.
Tokens are embedded in the callback URL to prevent unauthorized submissions.
Supports both call records and video transcriptions.
Uses API_HOSTNAME_WITHOUT_PORT constant for environment-aware URLs:
- Production: api.warmlyyours.com
- Staging: api.warmlyyours.ws
- Development: Uses dev tunnel (api-hostname.warmlyyours.dev)
Constant Summary collapse
- JWT_SECRET =
Rails.application.secret_key_base
- JWT_ALGORITHM =
'HS256'- TOKEN_EXPIRY =
24.hours
- SUPPORTED_RESOURCE_TYPES =
%w[CallRecord Video].freeze
Class Method Summary collapse
-
.call_record_webhook_url(call_record_id:) ⇒ Object
Backwards-compatible methods for call records.
-
.callback_url(resource_type:, resource_id:) ⇒ String
Generate a callback URL for production/staging.
-
.dev_callback_url(resource_type:, resource_id:) ⇒ String
Generate a dev callback URL for testing via Cloudflare tunnel.
-
.generate_token(resource_type:, resource_id:) ⇒ String
Generate a JWT token for callback authentication.
-
.valid_token?(token) ⇒ Boolean
Check if a token is valid.
-
.validate_token(token) ⇒ Hash?
Validate and decode a JWT token.
-
.video_webhook_url(video_id:) ⇒ Object
Methods for videos.
-
.webhook_url(resource_type:, resource_id:) ⇒ String
Get the appropriate callback URL based on environment.
Class Method Details
.call_record_webhook_url(call_record_id:) ⇒ Object
Backwards-compatible methods for call records
112 113 114 |
# File 'app/services/assemblyai_callback_token_service.rb', line 112 def call_record_webhook_url(call_record_id:) webhook_url(resource_type: 'CallRecord', resource_id: call_record_id) end |
.callback_url(resource_type:, resource_id:) ⇒ String
Generate a callback URL for production/staging
84 85 86 87 |
# File 'app/services/assemblyai_callback_token_service.rb', line 84 def callback_url(resource_type:, resource_id:) token = generate_token(resource_type: resource_type, resource_id: resource_id) "https://#{API_HOSTNAME_WITHOUT_PORT}/webhooks/v1/assemblyai?token=#{token}" end |
.dev_callback_url(resource_type:, resource_id:) ⇒ String
Generate a dev callback URL for testing via Cloudflare tunnel
93 94 95 96 97 |
# File 'app/services/assemblyai_callback_token_service.rb', line 93 def dev_callback_url(resource_type:, resource_id:) hostname = `hostname -s`.strip.downcase token = generate_token(resource_type: resource_type, resource_id: resource_id) "https://api-#{hostname}.warmlyyours.dev/webhooks/v1/assemblyai?token=#{token}" end |
.generate_token(resource_type:, resource_id:) ⇒ String
Generate a JWT token for callback authentication
34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'app/services/assemblyai_callback_token_service.rb', line 34 def generate_token(resource_type:, resource_id:) raise ArgumentError, "Unsupported resource type: #{resource_type}" unless SUPPORTED_RESOURCE_TYPES.include?(resource_type) payload = { purpose: 'assemblyai_callback', resource_type: resource_type, resource_id: resource_id, exp: TOKEN_EXPIRY.from_now.to_i, iat: Time.current.to_i, jti: SecureRandom.uuid # Unique token ID } JWT.encode(payload, JWT_SECRET, JWT_ALGORITHM) end |
.valid_token?(token) ⇒ Boolean
Check if a token is valid
76 77 78 |
# File 'app/services/assemblyai_callback_token_service.rb', line 76 def valid_token?(token) validate_token(token).present? end |
.validate_token(token) ⇒ Hash?
Validate and decode a JWT token
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'app/services/assemblyai_callback_token_service.rb', line 52 def validate_token(token) return nil if token.blank? decoded_token = JWT.decode(token, JWT_SECRET, true, { algorithm: JWT_ALGORITHM }) payload = decoded_token.first # Check if token is for AssemblyAI callback return nil unless payload['purpose'] == 'assemblyai_callback' # Normalize for backwards compatibility (old tokens used call_record_id) if payload['call_record_id'] && !payload['resource_type'] payload['resource_type'] = 'CallRecord' payload['resource_id'] = payload['call_record_id'] end payload rescue JWT::DecodeError, JWT::ExpiredSignature => e Rails.logger.warn "[AssemblyAI Callback] Token validation failed: #{e.class}" nil end |
.video_webhook_url(video_id:) ⇒ Object
Methods for videos
117 118 119 |
# File 'app/services/assemblyai_callback_token_service.rb', line 117 def video_webhook_url(video_id:) webhook_url(resource_type: 'Video', resource_id: video_id) end |
.webhook_url(resource_type:, resource_id:) ⇒ String
Get the appropriate callback URL based on environment
103 104 105 106 107 108 109 |
# File 'app/services/assemblyai_callback_token_service.rb', line 103 def webhook_url(resource_type:, resource_id:) if Rails.env.development? dev_callback_url(resource_type: resource_type, resource_id: resource_id) else callback_url(resource_type: resource_type, resource_id: resource_id) end end |