Skip to content
Esc
  • OverviewGuidesWhat exists today, and where to start.
  • QuickstartGuidesKey, domain, first send — in that order.
  • AuthenticationGuidesBearer keys, the mandatory User-Agent, and what each refusal means.
  • ErrorsGuidesThe whole vocabulary, with the status each name carries.
  • IdempotencyGuidesRetry a send without sending it twice.
  • PaginationGuidesCursors are item IDs, not page numbers.
  • Rate limitsGuidesTen a second per team, and the headers that tell you where you are.
  • EventsGuidesEvery event a webhook can carry, with one real payload each.
  • DomainsGuidesThe records, where they go at each registrar, and what the page does while you wait.
  • TrackingGuidesOpens and clicks: one record, two toggles, and what an open really means.
  • ReceivingGuidesInbound mail, and the Inbox: a webhook fires, you read it, you answer it.
  • InboxGuidesChannels, personal mailboxes and seats: who sees what, and where a reply goes.
  • Node SDKGuidesThe rasket package: typed from the API's own document, retries only what is safe.
  • Python SDKGuidesThe rasket package on PyPI: the Node client's methods, in snake_case, over httpx.
  • MCP serverGuidesConnect Claude, ChatGPT or any MCP client: your scopes, no key.
  • AI assistGuidesSubject lines, drafts and diagnosis — in the dashboard and over the API, off until you allow it.
  • AgentsGuidesLet an AI agent set Rasket up: the skill, the rules file, MCP, and the recipe they share.
  • OAuthGuidesLet another app act for a team: register, authorize with PKCE, exchange, refresh.
  • Single sign-onGuidesOIDC login for your team, a domain proved by DNS, enforcement and break-glass.
  • IntegrationsGuidesVercel, Netlify and Cloudflare, plus Zapier and n8n for workflows without code.
  • SMTPGuidesSend from anything that speaks SMTP: settings, setup guides, limits and replies.
  • ZapierGuidesSend email, add contacts and react to email events from a Zap, with no code.
  • n8nGuidesThe Rasket node and trigger for n8n workflows: install, connect, every operation.
  • EmailsAPI referenceSend, batch, retrieve, list, reschedule, cancel, attachments.
  • DomainsAPI referenceAdd a domain, publish its records, verify it.
  • API keysAPI referenceCreate, list, rename and revoke credentials.
  • WebhooksAPI referencePayloads, signature verification, retries and replay.
  • SuppressionsAPI referenceAddresses we will not send to, and why.
  • LogsAPI referenceEvery request made with this team's credentials.
  • MetricsAPI referenceDelivery, bounce, complaint and engagement counts.
  • TemplatesAPI referenceVersioned email content with typed variables, addressed by ID or alias.
  • ContactsAPI referenceYour audience: contacts, their typed properties, segments and topic choices.
  • SegmentsAPI referenceAudiences defined by a filter, by hand, or both.
  • TopicsAPI referenceWhat contacts subscribe to, and the preference page's list.
  • CampaignsAPI referenceCampaigns, at /broadcasts: one message to a segment, from draft to results.
  • ImportsAPI referenceCSV uploads: column mapping, conflicts and counts.
  • AutomationsAPI referenceWorkflows that run per contact: the graph, its versions, and every run.
  • Custom eventsAPI referenceThe names your product fires, and what starts a workflow.
  • ReceivingAPI referenceMail sent to you: the message, its attachments, its raw source.
  • OAuthAPI referenceClient registration, the token endpoint, and the grants a team has given.
  • TeamAPI referenceThe team a credential belongs to: its plan, sender identity, AI flag and members.
  • BillingAPI referencePlan, usage, invoices and add-ons, and the hosted pages where a customer pays.
  • AI helpersAPI referenceSubject lines, a first draft, and why an email did what it did.

API referenceWebhooks

Webhooks

We POST a signed JSON payload to your URL when something happens on your team: an email is sent, delivered, bounces or arrives, a contact, domain or suppression changes, or an automation runs. This page is the payload, the signature, and what we do when your endpoint is down.

The payload

Every event has the same envelope: a type, the created_at of the event itself, and a data object describing the email. Types that carry more detail add one extra key inside data.

{
  "type": "email.bounced",
  "created_at": "2026-09-09T10:16:44.902Z",
  "data": {
    "email_id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
    "from": "Acme <orders@send.acme.example>",
    "to": ["ronald.williams@example.com"],
    "subject": "Your order has shipped",
    "message_id": "<01000199a3c4d5e6-7f8a9b0c@send.acme.example>",
    "created_at": "2026-09-09T10:14:02.118Z",
    "tags": { "order": "1042" },
    "bounce": {
      "type": "Permanent",
      "subType": "General",
      "message": "smtp; 550 5.1.1 The email account that you tried to reach does not exist.",
      "diagnosticCode": ["smtp; 550 5.1.1 user unknown"]
    }
  }
}

data always carries email_id, from, to, subject, message_id and the email's own created_at. tags and headers appear when the email had them. For per-recipient events — bounce, complaint, delivery — to lists the recipients that event is about, not everyone the message went to.

Event-specific objects
TypeExtra keyFields
email.bouncedbouncetype, subType, message, diagnosticCode[]
email.openedopenipAddress, timestamp, userAgent
email.clickedclickipAddress, link, timestamp, userAgent
email.failedfailedreason
email.suppressedsuppressedreason, type, message, diagnosticCode[]
email.delivery_delayeddelaytype, expirationTime, delayedRecipients[]
email.sent, email.scheduled, email.delivered, email.complained—The envelope, with no extra object.

Event types

An endpoint subscribes to one or more of these. Any other type is refused when you create or update the endpoint.

  • email.sent
  • email.scheduled
  • email.delivered
  • email.delivery_delayed
  • email.bounced
  • email.complained
  • email.opened
  • email.clicked
  • email.failed
  • email.suppressed
  • email.canceled
  • domain.created
  • domain.updated
  • domain.deleted
  • suppression.added
  • suppression.removed
  • contact.created
  • contact.updated
  • contact.deleted
  • email.received
  • automation.run.started
  • automation.run.completed
  • automation.run.failed

Verifying a signature

Every delivery carries three headers. Reject anything that does not verify — the URL is public, and the signature is the only thing that says the request came from us.

Headers we send
HeaderMeaning
svix-idThe event's ID. Stable across replays — dedupe on this.
svix-timestampUnix seconds. Reject anything more than five minutes old.
svix-signatureOne or more `v1,<base64>` signatures, space separated. Accept the payload if any of them verifies.
user-agentRasket-Webhooks/1.0

The signature covers the raw request body. Any framework that parses JSON before your handler runs has already destroyed the bytes we signed — key order and whitespace both change when a parsed object is serialized again, and the signature will never match. Read the body as text or bytes first, verify, then parse.

The contract

Five steps, identical in every language:

verify(rawBody, headers, secret) -> payload

  1. require svix-id, svix-timestamp, svix-signature
  2. reject if |now - timestamp| > 300 s
  3. expected = base64(hmac_sha256(
       base64decode(secret without the whsec_ prefix),
       `${id}.${timestamp}.${rawBody}`
     ))
  4. for each "v1,<sig>" in svix-signature (space separated):
       constant-time compare against expected
  5. any match -> JSON.parse(rawBody); none -> throw

All seven recipes use the official Svix libraries today: the scheme is Svix-compatible, so they verify our signatures as they are. @rasket/webhook-verify, our own zero-dependency verifier, follows when it is published. Every recipe reads the raw body first; that is the line that matters.

Node

// @rasket/webhook-verify, our own zero-dependency verifier, is not on npm yet. The
// scheme is Svix-compatible, so the official svix package verifies it today.
// npm install svix
import { createServer } from "node:http";
import { Webhook } from "svix";

const wh = new Webhook(process.env.RASKET_WEBHOOK_SECRET);

createServer((req, res) => {
  // Collect the raw bytes. Nothing parses them: req.body does not exist here, which is
  // the one thing a bare Node server has going for it.
  const chunks = [];
  req.on("data", (chunk) => chunks.push(chunk));
  req.on("end", () => {
    const rawBody = Buffer.concat(chunks);

    try {
      const event = wh.verify(rawBody, req.headers);
      handle(event);
      res.writeHead(200).end();
    } catch {
      // A missing header, a timestamp more than five minutes off, or no matching signature.
      res.writeHead(400).end();
    }
  });
}).listen(3000);

Next.js

// app/api/hooks/rasket/route.ts
// @rasket/webhook-verify, our own zero-dependency verifier, is not on npm yet. The
// scheme is Svix-compatible, so the official svix package verifies it today.
// npm install svix
import { Webhook } from "svix";

const wh = new Webhook(process.env.RASKET_WEBHOOK_SECRET);

export async function POST(request: Request) {
  // await request.text(), NOT request.json(). Parsing to an object and serializing it
  // again is not the identity function — key order and whitespace both change, and the
  // signature is over the bytes we sent.
  const rawBody = await request.text();

  try {
    // request.headers is a Headers; the verifier wants a plain object.
    const event = wh.verify(rawBody, Object.fromEntries(request.headers));
    await handle(event);
  } catch {
    return new Response("invalid signature", { status: 400 });
  }

  return new Response("ok", { status: 200 });
}

Express

// @rasket/webhook-verify, our own zero-dependency verifier, is not on npm yet. The
// scheme is Svix-compatible, so the official svix package verifies it today.
// npm install svix
import express from "express";
import { Webhook } from "svix";

const app = express();
const wh = new Webhook(process.env.RASKET_WEBHOOK_SECRET);

// express.raw(), NOT express.json(). Mount it on this route only and BEFORE any global
// body parser, so the rest of your application still gets parsed bodies.
app.post("/hooks/rasket", express.raw({ type: "application/json" }), (req, res) => {
  // req.body is a Buffer here — the exact bytes we signed.
  try {
    const event = wh.verify(req.body, req.headers);
    handle(event);
    res.sendStatus(200);
  } catch {
    res.status(400).send("invalid signature");
  }
});

app.use(express.json()); // everything else, after the webhook route

Python

# pip install svix
from flask import Flask, request
from svix.webhooks import Webhook, WebhookVerificationError

app = Flask(__name__)


@app.post("/hooks/rasket")
def rasket_webhook():
    # request.get_data() is the raw body. Never request.get_json() first:
    # it parses, and the signature is over what arrived.
    raw_body = request.get_data()

    try:
        event = Webhook(RASKET_WEBHOOK_SECRET).verify(raw_body, dict(request.headers))
    except WebhookVerificationError:
        return "invalid signature", 400

    handle(event)
    return "", 200


# FastAPI is the same shape: raw_body = await request.body()

Go

// go get github.com/svix/svix-webhooks/go
package main

import (
	"io"
	"net/http"
	"os"

	svix "github.com/svix/svix-webhooks/go"
)

func rasketWebhook(w http.ResponseWriter, r *http.Request) {
	// io.ReadAll on r.Body, before anything decodes it. json.NewDecoder(r.Body)
	// consumes the reader, and the bytes are gone.
	rawBody, err := io.ReadAll(r.Body)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	wh, err := svix.NewWebhook(os.Getenv("RASKET_WEBHOOK_SECRET"))
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	if err := wh.Verify(rawBody, r.Header); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	handle(rawBody) // decode it now, after the signature checked out
	w.WriteHeader(http.StatusOK)
}

Ruby

# gem install svix
require "sinatra"
require "svix"

post "/hooks/rasket" do
  # request.body.read is the raw body. Do not use params or a JSON middleware:
  # both parse, and the signature is over what arrived.
  raw_body = request.body.read
  request.body.rewind

  begin
    wh = Svix::Webhook.new(ENV["RASKET_WEBHOOK_SECRET"])
    event = wh.verify(raw_body, request.env)
  rescue Svix::WebhookVerificationError
    halt 400, "invalid signature"
  end

  handle(event)
  status 200
end

PHP

<?php
// composer require svix/svix
use Svix\Webhook;
use Svix\Exception\WebhookVerificationException;

// file_get_contents("php://input") is the raw body. $_POST is a parsed form and
// json_decode of it is a different string from the one we signed.
$rawBody = file_get_contents("php://input");

$headers = [
    "svix-id" => $_SERVER["HTTP_SVIX_ID"] ?? "",
    "svix-timestamp" => $_SERVER["HTTP_SVIX_TIMESTAMP"] ?? "",
    "svix-signature" => $_SERVER["HTTP_SVIX_SIGNATURE"] ?? "",
];

try {
    $wh = new Webhook(getenv("RASKET_WEBHOOK_SECRET"));
    $event = $wh->verify($rawBody, $headers);
} catch (WebhookVerificationException $e) {
    http_response_code(400);
    exit("invalid signature");
}

handle($event);
http_response_code(200);

Rotation

POST /webhooks/{id}/rotate-secret issues a new secret and keeps the previous one signing for 24 hours. During the overlap svix-signature carries both signatures, space separated. A verifier that accepts any matching signature — as the code above does — needs no downtime and no coordinated deploy. Both secrets start with whsec_ and are shown once.

Ordering and duplicates

  • Events are not ordered. A delivery can arrive before the sent event that logically precedes it. Key your handler on email_id plus type plus created_at rather than on arrival order.
  • Events can arrive more than once. Dedupe on svix-id, which is stable for the life of an event and does not change when it is replayed.
  • Answer with any 2xx as soon as you have stored the event. Do your work afterwards; a handler that finishes its work before replying will eventually time out and be retried.

Retries

Anything that is not a 2xx within ten seconds is a failure: a timeout, a DNS or TLS error, a refused connection, or a 3xx — we never follow redirects, so a redirect is a failed delivery, not a hop. We try up to ten times in all — the first delivery and nine retries — with ±10% jitter on every delay so a recovering outage does not get a thundering herd.

The retry schedule
AttemptDelay before it
1immediately
25 s
330 s
42 m
510 m
630 m
71 h
82 h
94 h
108 h

After the tenth attempt the event is marked failed and we stop. Nothing is lost — it stays readable, and you can replay it. An endpoint with no successful delivery for five consecutive days is disabled automatically and the team's admins are emailed; events keep being recorded for it while it is off.

Endpoints

Create an endpoint

POST /webhooks

Subscribe a URL to the events you care about.

Body

  • endpointstringRequired

    An absolute https URL. Redirects are never followed, so give the final one.

  • eventsstring[]Required

    At least one event type from the Events list. An unknown type is refused.

Request

curl -X POST "https://api.rasket.com/webhooks" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "endpoint": "https://acme.example.com/hooks/rasket",
  "events": ["email.delivered", "email.bounced", "email.complained"]
}'

Response 201

{
  "object": "webhook",
  "id": "3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38",
  "signing_secret": "whsec_5+7nYV7zXzDkm0guFcrnUAcgHhJhew/t"
}
  • signing_secret is returned by this response and never again in full. Every later read shows it masked.
  • The URL is checked against blocked (private and internal) address ranges when you create it, and again on every delivery.
  • How many endpoints a team may have depends on its plan.

List endpoints

GET /webhooks

Every endpoint on the team.

Query parameters

  • limitinteger

    How many items to return, 1–100. Defaults to 20.

  • afterstring

    Return the page that follows this item ID. Mutually exclusive with before.

  • beforestring

    Return the page that precedes this item ID. Mutually exclusive with after.

Request

curl -X GET "https://api.rasket.com/webhooks" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "id": "3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38",
      "endpoint": "https://acme.example.com/hooks/rasket",
      "events": ["email.delivered", "email.bounced", "email.complained"],
      "status": "enabled",
      "created_at": "2026-09-09T09:20:31.004Z"
    }
  ]
}

Retrieve an endpoint

GET /webhooks/{webhook_id}

One endpoint and its subscriptions.

Path parameters

  • webhook_idstringRequired

    The endpoint's ID.

Request

curl -X GET "https://api.rasket.com/webhooks/3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "webhook",
  "id": "3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38",
  "endpoint": "https://acme.example.com/hooks/rasket",
  "events": ["email.delivered", "email.bounced", "email.complained"],
  "status": "enabled",
  "signing_secret": "whsec_••••••••Ab3d",
  "created_at": "2026-09-09T09:20:31.004Z"
}
  • signing_secret comes back masked. Revealing it in full is a dashboard action and is audited.

Update an endpoint

PATCH /webhooks/{webhook_id}

Change the URL, the subscriptions, or turn it off.

Path parameters

  • webhook_idstringRequired

    The endpoint's ID.

Body

  • endpointstring

    A new absolute https URL.

  • eventsstring[]

    Replaces the subscription list.

  • statusstring

    enabled or disabled.

Request

curl -X PATCH "https://api.rasket.com/webhooks/3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "events": ["email.delivered", "email.bounced", "email.complained", "email.failed"]
}'

Response 200

{
  "object": "webhook",
  "id": "3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38"
}
  • Re-enabling an endpoint resumes new events only. Older ones are replayed explicitly.

Delete an endpoint

DELETE /webhooks/{webhook_id}

Stop delivering to this URL.

Path parameters

  • webhook_idstringRequired

    The endpoint's ID.

Request

curl -X DELETE "https://api.rasket.com/webhooks/3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "webhook",
  "id": "3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38",
  "deleted": true
}

List events

GET /webhooks/{webhook_id}/events

What we tried to deliver to this endpoint.

Path parameters

  • webhook_idstringRequired

    The endpoint's ID.

Query parameters

  • limitinteger

    How many items to return, 1–100. Defaults to 20.

  • afterstring

    Return the page that follows this item ID. Mutually exclusive with before.

  • beforestring

    Return the page that precedes this item ID. Mutually exclusive with after.

4 more fields (status, type, start_date, end_date)
  • statusstring

    Only events in this state: pending, attempting, success or failed.

  • typestring

    Only events of this type, such as email.bounced.

  • start_datestring

    ISO 8601 instant, inclusive. A calendar date alone is 422 invalid_parameter — send 2026-09-09T00:00:00.000Z.

  • end_datestring

    ISO 8601 instant, inclusive. Must be at or after start_date, or the request is 422 invalid_parameter.

Request

curl -X GET "https://api.rasket.com/webhooks/3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38/events" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "list",
  "has_more": true,
  "data": [
    {
      "id": "msg_0198f4c2a1b27c3e9d4f5a6b7c8d9e0f",
      "type": "email.delivered",
      "created_at": "2026-09-09T10:14:09.331Z",
      "status": "success"
    }
  ]
}
  • Event IDs start with msg_ and are the value of the svix-id header we sent.

Retrieve an event

GET /webhooks/{webhook_id}/events/{event_id}

One event, with the exact payload we signed.

Path parameters

  • webhook_idstringRequired

    The endpoint's ID.

  • event_idstringRequired

    The event's msg_ ID.

Request

curl -X GET "https://api.rasket.com/webhooks/3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38/events/msg_0198f4c2a1b27c3e9d4f5a6b7c8d9e0f" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "webhook_event",
  "id": "msg_0198f4c2a1b27c3e9d4f5a6b7c8d9e0f",
  "type": "email.delivered",
  "status": "success",
  "attempt_count": 1,
  "last_http_status": 200,
  "next_attempt_at": null,
  "created_at": "2026-09-09T10:14:09.331Z",
  "payload": {
    "type": "email.delivered",
    "created_at": "2026-09-09T10:14:09.331Z",
    "data": {
      "email_id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c"
    }
  }
}

List attempts

GET /webhooks/{webhook_id}/events/{event_id}/attempts

Every delivery we made for one event, and what came back.

Path parameters

  • webhook_idstringRequired

    The endpoint's ID.

  • event_idstringRequired

    The event's msg_ ID.

Request

curl -X GET "https://api.rasket.com/webhooks/3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38/events/msg_0198f4c2a1b27c3e9d4f5a6b7c8d9e0f/attempts" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "id": "atmpt_0198f4c2a1c07d4e8f9a0b1c2d3e4f50",
      "attempt_number": 1,
      "http_status_code": 200,
      "response": "ok",
      "duration_ms": 142,
      "error": null,
      "sent_at": "2026-09-09T10:14:09.480Z"
    }
  ]
}
  • Every attempt, oldest first, in one response: the list is not paginated.
  • Your response body is stored, truncated to 8 KB. Do not answer with anything secret.
  • error names a failure other than your status code — timeout (we wait 10 seconds), redirect_not_followed, a blocked address — and is null when your endpoint simply answered.

Replay an event

POST /webhooks/{webhook_id}/events/{event_id}/replay

Send the same bytes again, after you have fixed your handler.

Path parameters

  • webhook_idstringRequired

    The endpoint's ID.

  • event_idstringRequired

    The event's msg_ ID.

Request

curl -X POST "https://api.rasket.com/webhooks/3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38/events/msg_0198f4c2a1b27c3e9d4f5a6b7c8d9e0f/replay" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "webhook_event",
  "id": "msg_0198f4c2a1b27c3e9d4f5a6b7c8d9e0f"
}
  • A replay re-sends the identical payload with the same svix-id and a fresh timestamp and signature — so a handler that dedupes on svix-id will correctly ignore it.
  • Ten replays per minute per team. Replaying to a disabled endpoint is refused.

Rotate the signing secret

POST /webhooks/{webhook_id}/rotate-secret

Issue a new secret with a 24-hour overlap.

Path parameters

  • webhook_idstringRequired

    The endpoint's ID.

Request

curl -X POST "https://api.rasket.com/webhooks/3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38/rotate-secret" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "webhook",
  "id": "3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38",
  "signing_secret": "whsec_p+Fkd5b/AivqjtAqgqF1kw8jN803lMUi"
}
  • For 24 hours both secrets sign, and svix-signature carries both signatures space-separated. Accept any one that verifies and the rotation needs no downtime.
  • The new secret is shown once, exactly like the first one.

List parked events

GET /webhooks/{webhook_id}/parked

The events created while the endpoint was disabled, oldest first.

Path parameters

  • webhook_idstringRequired

    The endpoint's ID.

Query parameters

  • limitinteger

    How many to return, 1–100. has_more says whether more are parked.

Request

curl -X GET "https://api.rasket.com/webhooks/3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38/parked?limit=20" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "id": "msg_0198f4c2a1b27c3e9d4f5a6b7c8d9e0f",
      "type": "email.delivered",
      "created_at": "2026-09-09T09:20:33.412Z",
      "status": "failed",
      "attempt_count": 6,
      "last_http_status": 503
    }
  ]
}
  • A disabled endpoint keeps receiving events without delivering them. They are listed oldest first, the order they are delivered in.

Deliver parked events

POST /webhooks/{webhook_id}/parked/deliver

Replay the oldest parked events, in order, within the replay budget.

Path parameters

  • webhook_idstringRequired

    The endpoint's ID.

Request

curl -X POST "https://api.rasket.com/webhooks/3b7f21c0-d9a4-4e8b-b6c2-7a1f5d0e9c38/parked/deliver" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "parked_delivery",
  "delivered": 10,
  "remaining": 32,
  "stopped_reason": "rate_limit_exceeded"
}
  • At most 10 events per call, inside the same budget as a single replay: 10 a minute per team. When it runs out, delivery stops with stopped_reason: "rate_limit_exceeded".
  • Call again while remaining is above zero. A disabled endpoint is 422 validation_error: enable it first.