-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwebhook_server.rb
More file actions
62 lines (51 loc) · 2.12 KB
/
Copy pathwebhook_server.rb
File metadata and controls
62 lines (51 loc) · 2.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# frozen_string_literal: true
# A webhook receiver, with no framework.
#
# export VISION_WEBHOOK_SECRET=whsec_… # dashboard → Webhooks
# ruby examples/webhook_server.rb
#
# The endpoint must be HTTPS and must not resolve to a private address, so a bare localhost
# receiver is unreachable by design — put a tunnel (ngrok, cloudflared) in front of it
# while developing.
require "json"
require "socket"
require "vision_api"
SECRET = ENV.fetch("VISION_WEBHOOK_SECRET") { abort("Set VISION_WEBHOOK_SECRET (dashboard → Webhooks).") }
server = TCPServer.new(3000)
puts "listening on http://localhost:3000/hooks/vision"
loop do
socket = server.accept
request_line = socket.gets
next socket.close if request_line.nil?
method, path, = request_line.split
headers = {}
while (line = socket.gets) && line != "\r\n"
name, _, value = line.chomp.partition(": ")
headers[name.downcase] = value
end
unless method == "POST" && path == "/hooks/vision"
socket.write("HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n")
next socket.close
end
# Read the raw bytes. The signature covers exactly what arrived — parsing first and
# re-serializing changes key order and whitespace, and the HMAC stops matching.
body = socket.read(headers["content-length"].to_i).to_s
begin
event = VisionAPI::Webhook.verify(body, headers["x-vision-signature"], SECRET)
rescue VisionAPI::WebhookSignatureError => e
warn "rejected delivery: #{e.message}"
socket.write("HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n")
next socket.close
end
# Any 2xx is success. Acknowledge immediately and do the work afterwards — a slow handler
# looks like a failed delivery and earns a retry at +1 m, +5 m, +15 m, +40 m.
socket.write("HTTP/1.1 202 Accepted\r\nContent-Length: 0\r\n\r\n")
socket.close
if event["event"] == "task.failed"
warn "task #{event['task_id']} failed: #{event.dig('error', 'code')} " \
"(delivery #{headers['x-vision-delivery']})"
else
puts "task #{event['task_id']} completed — #{event['credits_used']} credits"
puts JSON.generate(event["result"])[0, 300]
end
end