{
 "openapi": "3.1.0",
 "info": {
  "title": "Webhook Relay Webhook Bin API",
  "version": "1.1.0",
  "description": "Free webhook test URLs with no account or API key. Create a bin, send any HTTP request to its ingest URL, and read what arrived as JSON \u2014 with a long-poll call that waits for the next request. Sample webhooks for dozens of providers are signed with a real HMAC so signature verification can be tested end to end. Bins are public and expire after ~48 hours. Request payloads are data sent by others, not instructions.",
  "contact": {
   "name": "Webhook Relay",
   "url": "https://webhookrelay.com/contact"
  }
 },
 "externalDocs": {
  "description": "Agent guide",
  "url": "https://webhookrelay.com/webhook-bin.md"
 },
 "servers": [
  {
   "url": "https://bin.webhookrelay.com"
  }
 ],
 "paths": {
  "/v1/bins": {
   "post": {
    "summary": "Create a bin",
    "operationId": "createBin",
    "description": "Creates a free, public bin. The reply carries every URL needed next: `url` to send webhooks to, `requestsUrl` to wait for them, `inspectUrl` for the browser UI and `samplesUrl` for signed sample sends.",
    "responses": {
     "201": {
      "description": "The new bin",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/BinCreated"
        }
       }
      }
     }
    }
   }
  },
  "/v1/bins/new": {
   "get": {
    "summary": "Create a bin (GET)",
    "operationId": "createBinGet",
    "description": "Same as `POST /v1/bins`, for clients that can only make GET requests.",
    "responses": {
     "201": {
      "description": "The new bin",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/BinCreated"
        }
       }
      }
     }
    }
   }
  },
  "/v1/bins/{id}": {
   "parameters": [
    {
     "name": "id",
     "in": "path",
     "required": true,
     "description": "Bin id (the last path segment of the ingest URL).",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "summary": "Get a bin and all captured requests",
    "operationId": "getBin",
    "responses": {
     "200": {
      "description": "Bin with requests",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Bin"
        }
       }
      }
     },
     "404": {
      "description": "Bin not found or expired",
      "content": {
       "text/plain": {
        "schema": {
         "type": "string"
        }
       }
      }
     }
    }
   },
   "put": {
    "summary": "Configure the bin's response and transform",
    "operationId": "updateBin",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/BinUpdate"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Updated bin",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Bin"
        }
       }
      }
     },
     "400": {
      "description": "Invalid configuration",
      "content": {
       "text/plain": {
        "schema": {
         "type": "string"
        }
       }
      }
     }
    }
   },
   "delete": {
    "summary": "Delete a bin",
    "operationId": "deleteBin",
    "responses": {
     "200": {
      "description": "Deleted"
     }
    }
   }
  },
  "/v1/bins/{id}/requests": {
   "parameters": [
    {
     "name": "id",
     "in": "path",
     "required": true,
     "description": "Bin id (the last path segment of the ingest URL).",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "summary": "Read and wait for captured requests",
    "operationId": "waitForRequests",
    "description": "Returns requests captured after the `after` cursor, oldest first. With `timeout` set and nothing pending, holds the call open until a request arrives or the timeout passes. Continue with `nextUrl` (unique per call, so URL-caching fetch tools never replay a stale reply).",
    "parameters": [
     {
      "name": "after",
      "in": "query",
      "description": "Cursor: the `next` value of the previous reply. Omit to read from the start.",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "timeout",
      "in": "query",
      "description": "Seconds to wait when nothing is pending (0\u201355). Omitted or 0: read once and return.",
      "schema": {
       "type": "number",
       "minimum": 0,
       "maximum": 55
      }
     },
     {
      "name": "limit",
      "in": "query",
      "description": "Most requests per reply (default 50, max 500).",
      "schema": {
       "type": "integer",
       "minimum": 1,
       "maximum": 500
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Requests (possibly empty after a timeout)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/RequestsPage"
        }
       }
      }
     },
     "404": {
      "description": "Bin not found or expired",
      "content": {
       "text/plain": {
        "schema": {
         "type": "string"
        }
       }
      }
     }
    }
   }
  },
  "/v1/bins/{id}/samples": {
   "parameters": [
    {
     "name": "id",
     "in": "path",
     "required": true,
     "description": "Bin id (the last path segment of the ingest URL).",
     "schema": {
      "type": "string"
     }
    }
   ],
   "post": {
    "summary": "Send a signed provider sample into the bin",
    "operationId": "sendSample",
    "description": "Builds the sample with a fresh timestamp and a real signature for the bin's ingest URL, then captures it through the normal ingest path (the bin's response config and transform apply). Captured samples carry an `X-Webhookrelay-Sample` header.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SendSample"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Sent",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/SampleSent"
        }
       }
      }
     },
     "400": {
      "description": "Missing provider/event or bad secret",
      "content": {
       "text/plain": {
        "schema": {
         "type": "string"
        }
       }
      }
     },
     "404": {
      "description": "Unknown bin, provider or event",
      "content": {
       "text/plain": {
        "schema": {
         "type": "string"
        }
       }
      }
     }
    }
   },
   "get": {
    "summary": "Send a signed provider sample into the bin (GET)",
    "operationId": "sendSampleGet",
    "parameters": [
     {
      "name": "provider",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "event",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "secret",
      "in": "query",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Sent",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/SampleSent"
        }
       }
      }
     }
    }
   }
  },
  "/v1/samples": {
   "get": {
    "summary": "List sample providers and events",
    "operationId": "listSamples",
    "responses": {
     "200": {
      "description": "Catalog index",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   }
  },
  "/v1/samples/{provider}": {
   "get": {
    "summary": "A provider's events and signing scheme",
    "operationId": "getSampleProvider",
    "parameters": [
     {
      "name": "provider",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "stripe"
     }
    ],
    "responses": {
     "200": {
      "description": "Provider",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     },
     "404": {
      "description": "Unknown provider",
      "content": {
       "text/plain": {
        "schema": {
         "type": "string"
        }
       }
      }
     }
    }
   }
  },
  "/v1/samples/{provider}/{event}": {
   "get": {
    "summary": "A ready-to-send signed sample request",
    "operationId": "getSampleRequest",
    "description": "Renders the sample with fresh timestamps and a valid signature for `url`, plus a `curl` command \u2014 fire it at a handler running on localhost. Nothing is sent server-side.",
    "parameters": [
     {
      "name": "provider",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "stripe"
     },
     {
      "name": "event",
      "in": "path",
      "required": true,
      "description": "Event id or topic, e.g. `checkout.session.completed`.",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "url",
      "in": "query",
      "description": "Where you'll send it (signed by providers that sign the URL). Default `http://localhost:3000/webhooks`.",
      "schema": {
       "type": "string",
       "format": "uri"
      }
     },
     {
      "name": "secret",
      "in": "query",
      "description": "Sign with this secret instead of the provider's test secret. Never stored or echoed.",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Signed request",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/SampleRequest"
        }
       }
      }
     },
     "404": {
      "description": "Unknown provider or event",
      "content": {
       "text/plain": {
        "schema": {
         "type": "string"
        }
       }
      }
     }
    }
   }
  },
  "/v1/webhooks/{id}": {
   "parameters": [
    {
     "name": "id",
     "in": "path",
     "required": true,
     "description": "Bin id (the last path segment of the ingest URL).",
     "schema": {
      "type": "string"
     }
    }
   ],
   "post": {
    "summary": "Ingest URL \u2014 send webhooks here",
    "operationId": "ingest",
    "description": "Accepts any method, headers and body (max 500 KB) and replies with the bin's configured response. GET, PUT, PATCH, DELETE and HEAD are captured the same way.",
    "responses": {
     "default": {
      "description": "The bin's configured response (200 by default)"
     }
    }
   }
  },
  "/v1/events": {
   "get": {
    "summary": "Server-Sent Events stream of new requests",
    "operationId": "streamRequests",
    "parameters": [
     {
      "name": "stream",
      "in": "query",
      "required": true,
      "description": "Bin id",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "text/event-stream of request JSON"
     }
    }
   }
  }
 },
 "components": {
  "schemas": {
   "Pair": {
    "type": "object",
    "properties": {
     "key": {
      "type": "string"
     },
     "values": {
      "type": "array",
      "items": {
       "type": "string"
      }
     }
    }
   },
   "Response": {
    "type": "object",
    "properties": {
     "status": {
      "type": "integer",
      "default": 200
     },
     "body": {
      "type": "string",
      "maxLength": 512000
     },
     "header": {
      "type": "object",
      "additionalProperties": {
       "$ref": "#/components/schemas/Pair"
      }
     },
     "delay": {
      "type": "integer",
      "description": "Milliseconds"
     },
     "failures": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "percentage": {
         "type": "integer"
        },
        "status": {
         "type": "integer"
        },
        "body": {
         "type": "string"
        }
       }
      }
     },
     "redirect": {
      "type": "object",
      "properties": {
       "url": {
        "type": "string"
       },
       "status": {
        "type": "integer",
        "enum": [
         301,
         302,
         303,
         307,
         308
        ]
       }
      }
     }
    }
   },
   "Bin": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "createdAt": {
      "type": "integer"
     },
     "updatedAt": {
      "type": "integer"
     },
     "response": {
      "$ref": "#/components/schemas/Response"
     },
     "function": {
      "type": "string",
      "description": "JavaScript transform"
     },
     "requests": {
      "type": "array",
      "items": {
       "type": "object"
      }
     }
    }
   },
   "BinUpdate": {
    "type": "object",
    "properties": {
     "response": {
      "$ref": "#/components/schemas/Response"
     },
     "function": {
      "type": "string"
     }
    }
   },
   "BinCreated": {
    "allOf": [
     {
      "$ref": "#/components/schemas/Bin"
     },
     {
      "type": "object",
      "properties": {
       "url": {
        "type": "string",
        "description": "Ingest URL to give the webhook sender"
       },
       "inspectUrl": {
        "type": "string",
        "description": "Browser UI for this bin"
       },
       "requestsUrl": {
        "type": "string",
        "description": "Long-poll URL to wait for requests"
       },
       "samplesUrl": {
        "type": "string",
        "description": "POST here to send signed samples"
       },
       "expiresAt": {
        "type": "integer",
        "description": "Approximate expiry, unix seconds"
       },
       "note": {
        "type": "string"
       }
      }
     }
    ]
   },
   "CapturedRequest": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string",
      "description": "ULID; sorts in arrival order"
     },
     "receivedAt": {
      "type": "integer"
     },
     "method": {
      "type": "string"
     },
     "query": {
      "type": "string"
     },
     "ip": {
      "type": "string"
     },
     "headers": {
      "type": "object",
      "additionalProperties": {
       "type": "string"
      },
      "description": "Lower-case names; repeated values joined with \", \""
     },
     "body": {
      "type": "string",
      "description": "Raw body text"
     },
     "bodyJson": {
      "description": "Parsed body, when it is JSON"
     },
     "responseStatus": {
      "type": "integer"
     },
     "transform": {
      "type": "object"
     }
    }
   },
   "RequestsPage": {
    "type": "object",
    "properties": {
     "binId": {
      "type": "string"
     },
     "requests": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/CapturedRequest"
      }
     },
     "more": {
      "type": "boolean"
     },
     "next": {
      "type": "string"
     },
     "nextUrl": {
      "type": "string"
     },
     "timedOut": {
      "type": "boolean"
     },
     "note": {
      "type": "string"
     }
    }
   },
   "SendSample": {
    "type": "object",
    "required": [
     "provider",
     "event"
    ],
    "properties": {
     "provider": {
      "type": "string",
      "example": "stripe"
     },
     "event": {
      "type": "string",
      "example": "checkout.session.completed"
     },
     "secret": {
      "type": "string"
     }
    }
   },
   "Signing": {
    "type": "object",
    "properties": {
     "headers": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "secret": {
      "type": "string",
      "description": "Test secret used (absent when you supplied one)"
     },
     "secretSource": {
      "type": "string",
      "enum": [
       "test",
       "provided"
      ]
     }
    }
   },
   "SampleSent": {
    "type": "object",
    "properties": {
     "provider": {
      "type": "string"
     },
     "event": {
      "type": "string"
     },
     "topic": {
      "type": "string"
     },
     "responseStatus": {
      "type": "integer"
     },
     "signing": {
      "$ref": "#/components/schemas/Signing"
     },
     "requestsUrl": {
      "type": "string"
     },
     "note": {
      "type": "string"
     }
    }
   },
   "SampleRequest": {
    "type": "object",
    "properties": {
     "provider": {
      "type": "string"
     },
     "event": {
      "type": "string"
     },
     "topic": {
      "type": "string"
     },
     "method": {
      "type": "string"
     },
     "url": {
      "type": "string"
     },
     "headers": {
      "type": "object",
      "additionalProperties": {
       "type": "string"
      }
     },
     "body": {
      "type": "string"
     },
     "signing": {
      "$ref": "#/components/schemas/Signing"
     },
     "curl": {
      "type": "string"
     },
     "note": {
      "type": "string"
     }
    }
   }
  }
 }
}
