Webhooks
Webhooks let you POST every submission to your own endpoint. Each form has its own list of webhooks. All endpoints require a bearer token.
Base URL: https://formspring.io/api/v1
| Method | Path | Ability |
|---|---|---|
| GET | /forms/{form}/webhooks |
webhooks:read |
| POST | /forms/{form}/webhooks |
webhooks:write |
| GET | /forms/{form}/webhooks/{webhook} |
webhooks:read |
| PUT | /forms/{form}/webhooks/{webhook} |
webhooks:write |
| DELETE | /forms/{form}/webhooks/{webhook} |
webhooks:write |
| GET | /forms/{form}/webhooks/{webhook}/deliveries |
webhooks:read |
| POST | /forms/{form}/webhooks/{webhook}/deliveries/{delivery}/replay |
webhooks:write |
List webhooks
GET /forms/{form}/webhooks
Returns every webhook attached to the form, including its signing secret hash (the actual secret was returned only at creation time — see below).
Response 200:
{
"data": [
{
"id": "wh_01H...",
"label": "Production Slack",
"provider": "slack",
"url": "https://hooks.slack.com/services/...",
"active": true,
"config": { "channel": "#submissions" },
"secret_last4": "a1f3",
"created_at": "2026-01-12T09:00:00Z"
}
]
}
Create a webhook
POST /forms/{form}/webhooks
Content-Type: application/json
{
"label": "Production Slack",
"provider": "slack",
"url": "https://hooks.slack.com/services/...",
"config": { "channel": "#submissions" }
}
provider is optional. Set it to one of slack, discord, teams, zapier, make, webhook (default) to format the payload for that target. Plain webhook posts the raw submission JSON.
Response 201:
{
"data": { "id": "wh_01H...", "label": "Production Slack", ... },
"secret": "whsec_abc123def456..."
}
The plaintext secret is shown once and never again. Save it somewhere your endpoint can access it. The secret is used to compute the X-Formspring-Signature header on every delivery so you can verify authenticity.
Show a webhook
GET /forms/{form}/webhooks/{webhook}
Returns the same shape as the list endpoint. The plaintext secret is never returned again — only secret_last4.
Update a webhook
PUT /forms/{form}/webhooks/{webhook}
Content-Type: application/json
Send only the fields you want to change. To rotate the signing secret, set "rotate_secret": true and the response includes a fresh secret field.
Response 200: updated WebhookResource. If rotate_secret was true, the response includes the new plaintext secret.
Delete a webhook
DELETE /forms/{form}/webhooks/{webhook}
Removes the webhook. In-flight deliveries continue but no new submissions trigger the webhook.
Response 200: {"ok": true}
List deliveries
GET /forms/{form}/webhooks/{webhook}/deliveries?per_page=50
Every delivery attempt is recorded. Each entry has the request body, response status, response body (truncated to 4KB), latency, and retry count.
Response 200:
{
"data": [
{
"id": "whd_01H...",
"status": "succeeded",
"http_status": 200,
"attempts": 1,
"duration_ms": 142,
"submission_id": "subm_01H...",
"request_body": { ... },
"response_body": "ok",
"delivered_at": "2026-05-07T12:34:56Z"
}
]
}
status is one of pending, succeeded, failed, dead. A delivery becomes dead after retries are exhausted.
Replay a delivery
POST /forms/{form}/webhooks/{webhook}/deliveries/{delivery}/replay
Re-fires the same delivery against the webhook's current URL and config. The original delivery row is preserved; a new delivery row is created for the replay.
Use cases:
- Your endpoint was down when the original fired.
- You changed the webhook URL and want to send historical events to the new endpoint.
- Debugging — you can replay the same payload against a tunnel like ngrok.
Response 200: {"ok": true, "delivery_id": "whd_02H..."}
Common errors
| Status | Meaning |
|---|---|
400 |
Invalid URL (must be HTTPS), invalid provider, or config validation failed |
401 |
Token missing or invalid |
403 |
Token lacks webhooks:read or webhooks:write |
404 |
Form, webhook, or delivery not found |
429 |
API rate limit hit |