| 36 | } |
| 37 | |
| 38 | func (h *handler) Handle(r *http.Request) (*event.Incoming, error) { |
| 39 | body, err := io.ReadAll(r.Body) |
| 40 | if err != nil { |
| 41 | return nil, err |
| 42 | } |
| 43 | defer r.Body.Close() |
| 44 | |
| 45 | // Sentry SDKs often send gzip or deflate compressed payloads. |
| 46 | body = decompress(body, r.Header.Get("Content-Encoding")) |
| 47 | |
| 48 | // Extract project from path: /api/{project}/store |
| 49 | project := resolveProject(extractProject(r.URL.Path)) |
| 50 | |
| 51 | // Try parsing as plain JSON first (legacy /store format). |
| 52 | var parsed map[string]any |
| 53 | if json.Unmarshal(body, &parsed) == nil { |
| 54 | uuid := "" |
| 55 | if id, ok := parsed["event_id"].(string); ok { |
| 56 | uuid = id |
| 57 | } |
| 58 | |
| 59 | // Resolve source code for browser/JS frames that only carry file+line. |
| 60 | if enriched, changed := enrichSourceContext(body, nil); changed { |
| 61 | body = enriched |
| 62 | } |
| 63 | |
| 64 | // Store structured data if DB is available. |
| 65 | h.storeJSONEvent(body, project) |
| 66 | |
| 67 | // Detect transactions (have spans + start_timestamp but no exception). |
| 68 | // Transactions are stored in structured tables but should not appear |
| 69 | // as empty cards in the global event feed. |
| 70 | if isTransaction(parsed) { |
| 71 | return nil, nil |
| 72 | } |
| 73 | |
| 74 | return &event.Incoming{ |
| 75 | UUID: uuid, |
| 76 | Type: "sentry", |
| 77 | Payload: json.RawMessage(body), |
| 78 | Project: project, |
| 79 | }, nil |
| 80 | } |
| 81 | |
| 82 | // Not valid JSON — parse as envelope format. |
| 83 | return h.handleEnvelope(body, project) |
| 84 | } |
| 85 | |
| 86 | // storeJSONEvent stores a plain JSON error event in structured tables. |
| 87 | func (h *handler) storeJSONEvent(body []byte, project string) { |