Public
Update atproto record after a delay, unless cancelled.
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

atproto-delay

A val.town webhook that schedules atproto XRPC requests to be replayed at a future time.

Clients POST a request body to the val (with an app password as a Bearer token); the request is stored, and a cron job fires it within 15 mins after the requested time.

Endpoint

POST /xrpc/me.byjp.atproto.repo.delayedRequest Authorization: Bearer <app-password> Content-Type: application/json

The app password is validated against the PDS for request.repo at submission time (via com.atproto.server.createSession). The cron job re-creates a session at execution time and uses the resulting accessJwt to call the target XRPC procedure.

Request body

fieldtypenotes
delay.keystringStable, client-chosen ID. Re-using the same key replaces the pending request; omitting request deletes it.
delay.executeAfterstringWhen to fire. See formats below.
requestobjectThe XRPC request to send. Must carry a $type field naming the procedure.

Supported request.$type values:

  • com.atproto.repo.putRecord
  • com.atproto.repo.deleteRecord

executeAfter formats

  • ISO 8601 timestamp — "2026-05-21T10:00:00.000Z"
  • ISO 8601 timestamp + additional delay — "2026-05-21T10:00:00.000Z/PT30M"
  • Unix ms — "1779354000000"
  • Unix ms + additional delay ms — "1779354000000+60000"

Example: schedule a record deletion

Delete at://did:plc:ephkzpinhaqcabtkugtbzrwu/fm.teal.alpha.actor.status/self one hour from now:

curl -X POST https://<your-val>.web.val.run/xrpc/me.byjp.atproto.repo.delayedRequest \ -H "Authorization: Bearer $APP_PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "delay": { "key": "clear-teal-status", "executeAfter": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'/PT1H" }, "request": { "$type": "com.atproto.repo.deleteRecord", "collection": "fm.teal.alpha.actor.status", "repo": "did:plc:ephkzpinhaqcabtkugtbzrwu", "rkey": "self" } }'

On success the val responds with { "ok": true, "dkey": "clear-teal-status", "executeAfter": <ms> }.

Example: cancel a pending request

Send the same delay.key with no request:

curl -X POST https://<your-val>.web.val.run/xrpc/me.byjp.atproto.repo.delayedRequest \ -H "Authorization: Bearer $APP_PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "delay": { "key": "clear-teal-status" } }'

Files

  • main.ts — HTTP trigger (the webhook)
  • execute.cron.ts — cron trigger that fires due requests (set the schedule to every minute in val.town)
  • db.ts — sqlite schema and queries
  • auth.ts — DID/PDS resolution and session creation
  • validate.ts — lexicon validation for stored requests
  • parse-time.tsexecuteAfter parsing