Class: Webhooks::V1::SftpgoController
- Inherits:
-
BaseController
- Object
- ActionController::API
- BaseController
- Webhooks::V1::SftpgoController
- Defined in:
- app/controllers/webhooks/v1/sftpgo_controller.rb
Overview
Webhook endpoint for SFTPGo's upload action hook — fires the moment the
PBX finishes uploading a call recording to the heatwave-sftp accessory, so
the recording imports in near-real-time instead of waiting for the hourly
poll.
SFTPGo (config/deploy.yml sftp accessory) is configured with
SFTPGO_COMMON__ACTIONS__EXECUTE_ON=upload
SFTPGO_COMMON__ACTIONS__HOOK=https://api.warmlyyours.com/webhooks/v1/sftpgo?token=…
and POSTs a JSON FsActionNotification on every completed upload:
{ "action": "upload", "username": "pbx", "status": 1,
"virtual_path": "/2026-06-19-…-2125551234.xml", "path": "…",
"file_size": 1234, "ip": "…", "timestamp": 1718… }
We trigger on the .xml sidecar, NOT the .wav: Switchvox writes a paired
<name>.wav + <name>.xml, and keying off the metadata file means the
importer always has both halves (if the wav lands fractionally later the
worker's retry covers it). The hook fires for every file/user, so we filter
hard to the pbx recordings user's metadata uploads and ACK everything else.
The derived .wav key is the SAME key form
CallRecordSwitchvoxObjectStore#importable_wav_keys yields, so the enqueued
CallRecordImporterWorker job shares the poll's :until_executed lock —
webhook and poll can never double-import the same recording. The hourly poll
stays in place as the backstop for anything the hook misses (xml-less wavs,
a dropped notification, SFTPGo downtime).
Authentication: shared-secret token query param (fails closed in
production when unconfigured). Test with:
curl -X POST "https://api.warmlyyours.com/webhooks/v1/sftpgo?token=TOKEN"
-H 'Content-Type: application/json'
-d '"action":"upload","username":"pbx","status":1,"virtual_path":"/x"action":"upload","username":"pbx","status":1,"virtual_path":"/x.xml"'
Constant Summary collapse
- RECORDINGS_USER =
The SFTPGo user the call-recording importer reads from (the paired
pbx-backupuser's system-backup uploads must NOT trigger an import). 'pbx'
Instance Method Summary collapse
-
#create ⇒ Object
POST /webhooks/v1/sftpgo.
Instance Method Details
#create ⇒ Object
POST /webhooks/v1/sftpgo
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'app/controllers/webhooks/v1/sftpgo_controller.rb', line 44 def create unless valid_request? Rails.logger.warn "[Webhooks::V1::Sftpgo] Unauthorized request from #{request.remote_ip}" return head :unauthorized end payload = json_payload return head :bad_request if payload.blank? wav_key = recordable_wav_key(payload) unless wav_key # A non-recording upload (a .wav sidecar event, a pbx-backup archive, an # archive move, an errored transfer): ACK so SFTPGo doesn't retry. Rails.logger.debug { "[Webhooks::V1::Sftpgo] Ignoring #{payload['action']} #{payload['username']}:#{payload['virtual_path'] || payload['path']}" } return head :ok end webhook_log = WebhookLog.ingest!( provider: 'sftpgo', category: 'recording_uploaded', external_id: wav_key, data: audit_data(payload, wav_key) ) WebhookProcessorWorker.perform_async(webhook_log.id) Rails.logger.info "[Webhooks::V1::Sftpgo] Queued import for #{wav_key} (WebhookLog #{webhook_log.id})" head :ok end |